Coverage Report


Files: 75
Lines: 13993
Covered: 3093 / 4153 (74.5%)


Lines covered: 67 / 116 (57.8%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IActivePool.sol";
6
import "./Interfaces/ICollSurplusPool.sol";
7
import "./Dependencies/ICollateralToken.sol";
8
import "./Interfaces/ICdpManagerData.sol";
9
import "./Dependencies/ERC3156FlashLender.sol";
10
import "./Dependencies/SafeERC20.sol";
11
import "./Dependencies/ReentrancyGuard.sol";
12
import "./Dependencies/AuthNoOwner.sol";
13
import "./Dependencies/BaseMath.sol";
14

                            
                        
15
/**
16
 * The Active Pool holds the collateral and EBTC debt (but not EBTC tokens) for all active cdps.
17
 *
18
 * When a cdp is liquidated, it's collateral and EBTC debt are transferred from the Active Pool, to either the
19
 * Stability Pool, the Default Pool, or both, depending on the liquidation conditions.
20
 */
21
contract ActivePool is IActivePool, ERC3156FlashLender, ReentrancyGuard, BaseMath, AuthNoOwner {
22
    using SafeERC20 for IERC20;
23
    string public constant NAME = "ActivePool";
24

                            
                        
25
    address public immutable borrowerOperationsAddress;
26
    address public immutable cdpManagerAddress;
27
    address public immutable collSurplusPoolAddress;
28
    address public feeRecipientAddress;
29

                            
                        
30
    uint256 internal systemCollShares; // deposited collateral tracker
31
    uint256 internal systemDebt;
32
    uint256 internal feeRecipientCollShares; // coll shares claimable by fee recipient
33
    ICollateralToken public collateral;
34

                            
                        
35
    // --- Contract setters ---
36

                            
                        
37
    /// @notice Constructor for the ActivePool contract
38
    /// @dev Initializes the contract with the borrowerOperationsAddress, cdpManagerAddress, collateral token address, collSurplusAddress, and feeRecipientAddress
39
    /// @param _borrowerOperationsAddress The address of the Borrower Operations contract
40
    /// @param _cdpManagerAddress The address of the Cdp Manager contract
41
    /// @param _collTokenAddress The address of the collateral token
42
    /// @param _collSurplusAddress The address of the collateral surplus pool
43
    /// @param _feeRecipientAddress The address of the fee recipient
44

                            
                        
45
    constructor(
46
        address _borrowerOperationsAddress,
47
        address _cdpManagerAddress,
48
        address _collTokenAddress,
49
        address _collSurplusAddress,
50
        address _feeRecipientAddress
51
    ) {
52
        borrowerOperationsAddress = _borrowerOperationsAddress;
53
        cdpManagerAddress = _cdpManagerAddress;
54
        collateral = ICollateralToken(_collTokenAddress);
55
        collSurplusPoolAddress = _collSurplusAddress;
56
        feeRecipientAddress = _feeRecipientAddress;
57

                            
                        
58
        // TEMP: read authority to avoid signature change
59
        address _authorityAddress = address(AuthNoOwner(cdpManagerAddress).authority());
60
        if (_authorityAddress != address(0)) {
61
            _initializeAuthority(_authorityAddress);
62
        }
63

                            
                        
64
        emit FeeRecipientAddressChanged(_feeRecipientAddress);
65
    }
66

                            
                        
67
    // --- Getters for public variables. Required by IPool interface ---
68

                            
                        
69
    /// @notice Amount of stETH collateral shares in the contract
70
    /// @dev Not necessarily equal to the the contract's raw systemCollShares balance - tokens can be forcibly sent to contracts
71
    /// @return uint256 The amount of systemCollShares allocated to the pool
72

                            
                        
73
    function getSystemCollShares() external view override returns (uint256) {
74
        return systemCollShares;
75
    }
76

                            
                        
77
    /// @notice Returns the systemDebt state variable
78
    /// @dev The amount of EBTC debt in the pool. Like systemCollShares, this is not necessarily equal to the contract's EBTC token balance - tokens can be forcibly sent to contracts
79
    /// @return uint256 The amount of EBTC debt in the pool
80

                            
                        
81
    function getSystemDebt() external view override returns (uint256) {
82
        return systemDebt;
83
    }
84

                            
                        
85
    /// @notice The amount of stETH collateral shares claimable by the fee recipient
86
    /// @return uint256 The amount of collateral shares claimable by the fee recipient
87

                            
                        
88
    function getFeeRecipientClaimableCollShares() external view override returns (uint256) {
89
        return feeRecipientCollShares;
90
    }
91

                            
                        
92
    // --- Pool functionality ---
93

                            
                        
94
    /// @notice Sends stETH collateral shares to a specified account
95
    /// @dev Only for use by system contracts, the caller must be either BorrowerOperations or CdpManager
96
    /// @param _account The address of the account to send stETH to
97
    /// @param _shares The amount of stETH shares to send
98

                            
                        
99
    function transferSystemCollShares(address _account, uint256 _shares) public override {
100
        _requireCallerIsBOorCdpM();
101

                            
                        
102
        uint256 cachedSystemCollShares = systemCollShares;
103
        require(cachedSystemCollShares >= _shares, "!ActivePoolBal");
104
        unchecked {
105
            // Can use unchecked due to above
106
            cachedSystemCollShares -= _shares; // Updating here avoids an SLOAD
107
        }
108

                            
                        
109
        systemCollShares = cachedSystemCollShares;
110

                            
                        
111
        emit SystemCollSharesUpdated(cachedSystemCollShares);
112
        emit CollSharesTransferred(_account, _shares);
113

                            
                        
114
        _transferCollSharesWithContractHooks(_account, _shares);
115
    }
116

                            
                        
117
    /// @notice Sends stETH to a specified account, drawing from both core shares and liquidator rewards shares
118
    /// @notice Liquidator reward shares are not tracked via internal accounting in the active pool and are assumed to be present in expected amount as part of the intended behavior of BorowerOperations and CdpManager
119
    /// @dev Liquidator reward shares are added when a cdp is opened, and removed when it is closed
120
    /// @dev closeCdp() or liqudations result in the actor (borrower or liquidator respectively) receiving the liquidator reward shares
121
    /// @dev Redemptions result in the shares being sent to the coll surplus pool for claiming by the CDP owner
122
    /// @dev Note that funds in the coll surplus pool, just like liquidator reward shares, are not tracked as part of the system CR or coll of a CDP.
123
    /// @dev Requires that the caller is either BorrowerOperations or CdpManager
124
    /// @param _account The address of the account to send systemCollShares and the liquidator reward to
125
    /// @param _shares The amount of systemCollShares to send
126
    /// @param _liquidatorRewardShares The amount of the liquidator reward shares to send
127

                            
                        
128
    function transferSystemCollSharesAndLiquidatorReward(
129
        address _account,
130
        uint256 _shares,
131
        uint256 _liquidatorRewardShares
132
    ) external override {
133
        _requireCallerIsBOorCdpM();
134

                            
                        
135
        uint256 cachedSystemCollShares = systemCollShares;
136
        require(cachedSystemCollShares >= _shares, "ActivePool: Insufficient collateral shares");
137
        uint256 totalShares = _shares + _liquidatorRewardShares; // TODO: Is this safe?
138
        unchecked {
139
            // Safe per the check above
140
            cachedSystemCollShares -= _shares;
141
        }
142
        systemCollShares = cachedSystemCollShares;
143

                            
                        
144
        emit SystemCollSharesUpdated(cachedSystemCollShares);
145
        emit CollSharesTransferred(_account, totalShares);
146

                            
                        
147
        _transferCollSharesWithContractHooks(_account, totalShares);
148
    }
149

                            
                        
150
    /// @notice Allocate stETH shares from the system to the fee recipient to claim at-will (pull model)
151
    /// @dev Requires that the caller is CdpManager
152
    /// @dev Only the current fee recipient address is able to claim the shares
153
    /// @dev If the fee recipient address is changed while outstanding claimable coll is available, only the new fee recipient will be able to claim the outstanding coll
154
    /// @param _shares The amount of systemCollShares to allocate to the fee recipient
155

                            
                        
156
    function allocateSystemCollSharesToFeeRecipient(uint256 _shares) external override {
157
        _requireCallerIsCdpManager();
158

                            
                        
159
        uint256 cachedSystemCollShares = systemCollShares;
160

                            
                        
161
        require(cachedSystemCollShares >= _shares, "ActivePool: Insufficient collateral shares");
162
        unchecked {
163
            // Safe per the check above
164
            cachedSystemCollShares -= _shares;
165
        }
166

                            
                        
167
        systemCollShares = cachedSystemCollShares;
168

                            
                        
169
        uint256 cachedFeeRecipientCollShares = feeRecipientCollShares + _shares;
170
        feeRecipientCollShares = cachedFeeRecipientCollShares;
171

                            
                        
172
        emit SystemCollSharesUpdated(cachedSystemCollShares);
173
        emit FeeRecipientClaimableCollSharesIncreased(cachedFeeRecipientCollShares, _shares);
174
    }
175

                            
                        
176
    /// @notice Helper function to transfer stETH shares to another address, ensuring to call hooks into other system pools if they are the recipient
177
    /// @param _account The address to transfer shares to
178
    /// @param _shares The amount of shares to transfer
179

                            
                        
180
    function _transferCollSharesWithContractHooks(address _account, uint256 _shares) internal {
181
        // NOTE: No need for safe transfer if the collateral asset is standard. Make sure this is the case!
182
        collateral.transferShares(_account, _shares);
183

                            
                        
184
        if (_account == collSurplusPoolAddress) {
185
            ICollSurplusPool(_account).increaseTotalSurplusCollShares(_shares);
186
        }
187
    }
188

                            
                        
189
    /// @notice Increases the tracked EBTC debt of the system by a specified amount
190
    /// @dev Managed by system contracts - requires that the caller is either BorrowerOperations or CdpManager
191
    /// @param _amount: The amount to increase the system EBTC debt by
192

                            
                        
193
    function increaseSystemDebt(uint256 _amount) external override {
194
        _requireCallerIsBOorCdpM();
195

                            
                        
196
        uint256 cachedSystemDebt = systemDebt + _amount;
197

                            
                        
198
        systemDebt = cachedSystemDebt;
199
        emit ActivePoolEBTCDebtUpdated(cachedSystemDebt);
200
    }
201

                            
                        
202
    /// @notice Decreases the tracked EBTC debt of the system by a specified amount
203
    /// @dev Managed by system contracts - requires that the caller is either BorrowerOperations or CdpManager
204
    /// @param _amount: The amount to decrease the system EBTC debt by
205

                            
                        
206
    function decreaseSystemDebt(uint256 _amount) external override {
207
        _requireCallerIsBOorCdpM();
208

                            
                        
209
        uint256 cachedSystemDebt = systemDebt - _amount;
210

                            
                        
211
        systemDebt = cachedSystemDebt;
212
        emit ActivePoolEBTCDebtUpdated(cachedSystemDebt);
213
    }
214

                            
                        
215
    // --- 'require' functions ---
216

                            
                        
217
    /// @notice Checks if the caller is BorrowerOperations
218
    function _requireCallerIsBorrowerOperations() internal view {
219
        require(
220
            msg.sender == borrowerOperationsAddress,
221
            "ActivePool: Caller is not BorrowerOperations"
222
        );
223
    }
224

                            
                        
225
    /// @notice Checks if the caller is either BorrowerOperations or CdpManager
226
    function _requireCallerIsBOorCdpM() internal view {
227
        require(
228
            msg.sender == borrowerOperationsAddress || msg.sender == cdpManagerAddress,
229
            "ActivePool: Caller is neither BorrowerOperations nor CdpManager"
230
        );
231
    }
232

                            
                        
233
    /// @notice Checks if the caller is CdpManager
234
    function _requireCallerIsCdpManager() internal view {
235
        require(msg.sender == cdpManagerAddress, "ActivePool: Caller is not CdpManager");
236
    }
237

                            
                        
238
    /// @notice Notify that stETH collateral shares have been recieved, updating internal accounting accordingly
239
    /// @param _value The amount of collateral to receive
240

                            
                        
241
    function increaseSystemCollShares(uint256 _value) external override {
242
        _requireCallerIsBorrowerOperations();
243

                            
                        
244
        uint256 cachedSystemCollShares = systemCollShares + _value;
245
        systemCollShares = cachedSystemCollShares;
246
        emit SystemCollSharesUpdated(cachedSystemCollShares);
247
    }
248

                            
                        
249
    // === Flashloans === //
250

                            
                        
251
    /// @notice Borrow assets with a flash loan
252
    /// @dev The Collateral checks may cause reverts if you trigger a fee change big enough
253
    ///         consider calling `cdpManagerAddress.syncGlobalAccountingAndGracePeriod()`
254
    /// @param receiver The address to receive the flash loan
255
    /// @param token The address of the token to loan
256
    /// @param amount The amount of tokens to loan
257
    /// @param data Additional data
258
    /// @return A boolean value indicating whether the operation was successful
259

                            
                        
260
    function flashLoan(
261
        IERC3156FlashBorrower receiver,
262
        address token,
263
        uint256 amount,
264
        bytes calldata data
265
    ) external override returns (bool) {
266
        require(amount > 0, "ActivePool: 0 Amount");
267
        uint256 fee = flashFee(token, amount); // NOTE: Check for `token` is implicit in the requires above // also checks for paused
268
        require(amount <= maxFlashLoan(token), "ActivePool: Too much");
269

                            
                        
270
        uint256 amountWithFee = amount + fee;
271
        uint256 oldRate = collateral.getPooledEthByShares(DECIMAL_PRECISION);
272

                            
                        
273
        collateral.transfer(address(receiver), amount);
274

                            
                        
275
        // Callback
276
        require(
277
            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == FLASH_SUCCESS_VALUE,
278
            "ActivePool: IERC3156: Callback failed"
279
        );
280

                            
                        
281
        // Transfer of (principal + Fee) from flashloan receiver
282
        collateral.transferFrom(address(receiver), address(this), amountWithFee);
283

                            
                        
284
        // Send earned fee to designated recipient
285
        collateral.transfer(feeRecipientAddress, fee);
286

                            
                        
287
        // Check new balance
288
        // NOTE: Invariant Check, technically breaks CEI but I think we must use it
289
        // NOTE: This means any balance > systemCollShares is stuck, this is also present in LUSD as is
290

                            
                        
291
        // NOTE: This check effectively prevents running 2 FL at the same time
292
        //  You technically could, but you'd be having to repay any amount below systemCollShares to get Fl2 to not revert
293
        require(
294
            collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares),
295
            "ActivePool: Must repay Balance"
296
        );
297
        require(
298
            collateral.sharesOf(address(this)) >= systemCollShares,
299
            "ActivePool: Must repay Share"
300
        );
301
        require(
302
            collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate,
303
            "ActivePool: Should keep same collateral share rate"
304
        );
305

                            
                        
306
        emit FlashLoanSuccess(address(receiver), token, amount, fee);
307

                            
                        
308
        return true;
309
    }
310

                            
                        
311
    /// @notice Calculate the flash loan fee for a given token and amount loaned
312
    /// @param token The address of the token to calculate the fee for
313
    /// @param amount The amount of tokens to calculate the fee for
314
    /// @return The amount of the flash loan fee
315

                            
                        
316
    function flashFee(address token, uint256 amount) public view override returns (uint256) {
317
        require(token == address(collateral), "ActivePool: collateral Only");
318
        require(!flashLoansPaused, "ActivePool: Flash Loans Paused");
319

                            
                        
320
        return (amount * feeBps) / MAX_BPS;
321
    }
322

                            
                        
323
    /// @notice Get the maximum flash loan amount for a specific token
324
    /// @dev Exclusively used here for stETH collateral, equal to the current balance of the pool
325
    /// @param token The address of the token to get the maximum flash loan amount for
326
    /// @return The maximum flash loan amount for the token
327
    function maxFlashLoan(address token) public view override returns (uint256) {
328
        if (token != address(collateral)) {
329
            return 0;
330
        }
331

                            
                        
332
        if (flashLoansPaused) {
333
            return 0;
334
        }
335

                            
                        
336
        return collateral.balanceOf(address(this));
337
    }
338

                            
                        
339
    // === Governed Functions === //
340

                            
                        
341
    /// @notice Claim outstanding shares for fee recipient, updating internal accounting and transferring the shares.
342
    /// @dev Call permissinos are managed via authority for flexibility, rather than gating call to just feeRecipient.
343
    /// @dev Is likely safe as an open permission though caution should be taken.
344
    /// @param _shares The amount of shares to claim to feeRecipient
345
    function claimFeeRecipientCollShares(uint256 _shares) external override requiresAuth {
346
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Calling this increases shares so do it first
347

                            
                        
348
        uint256 cachedFeeRecipientCollShares = feeRecipientCollShares;
349
        require(
350
            cachedFeeRecipientCollShares >= _shares,
351
            "ActivePool: Insufficient fee recipient coll"
352
        );
353

                            
                        
354
        unchecked {
355
            cachedFeeRecipientCollShares -= _shares;
356
        }
357

                            
                        
358
        feeRecipientCollShares = cachedFeeRecipientCollShares;
359
        emit FeeRecipientClaimableCollSharesDecreased(cachedFeeRecipientCollShares, _shares);
360

                            
                        
361
        collateral.transferShares(feeRecipientAddress, _shares);
362
    }
363

                            
                        
364
    /// @dev Function to move unintended dust that are not protected
365
    /// @notice moves given amount of given token (collateral is NOT allowed)
366
    /// @notice because recipient are fixed, this function is safe to be called by anyone
367

                            
                        
368
    function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {
369
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Accrue State First
370

                            
                        
371
        require(token != address(collateral), "ActivePool: Cannot Sweep Collateral");
372

                            
                        
373
        uint256 balance = IERC20(token).balanceOf(address(this));
374
        require(amount <= balance, "ActivePool: Attempt to sweep more than balance");
375

                            
                        
376
        address cachedFeeRecipientAddress = feeRecipientAddress; // Saves an SLOAD
377

                            
                        
378
        IERC20(token).safeTransfer(cachedFeeRecipientAddress, amount);
379

                            
                        
380
        emit SweepTokenSuccess(token, amount, cachedFeeRecipientAddress);
381
    }
382

                            
                        
383
    /// @notice Set new FeeRecipient
384
    /// @dev Previous fees are forfeited, if you wish to claim to previous address
385
    ///       call `claimFeeRecipientCollShares` first
386
    function setFeeRecipientAddress(address _feeRecipientAddress) external requiresAuth {
387
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Accrue State First
388

                            
                        
389
        require(
390
            _feeRecipientAddress != address(0),
391
            "ActivePool: Cannot set fee recipient to zero address"
392
        );
393

                            
                        
394
        feeRecipientAddress = _feeRecipientAddress;
395
        emit FeeRecipientAddressChanged(_feeRecipientAddress);
396
    }
397

                            
                        
398
    /// @notice Sets new Fee for FlashLoans
399
    function setFeeBps(uint256 _newFee) external requiresAuth {
400
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Accrue State First
401

                            
                        
402
        require(_newFee <= MAX_FEE_BPS, "ERC3156FlashLender: _newFee should <= MAX_FEE_BPS");
403

                            
                        
404
        // set new flash fee
405
        uint256 _oldFee = feeBps;
406
        feeBps = uint16(_newFee);
407
        emit FlashFeeSet(msg.sender, _oldFee, _newFee);
408
    }
409

                            
                        
410
    /// @notice Should Flashloans be paused?
411
    function setFlashLoansPaused(bool _paused) external requiresAuth {
412
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Accrue State First
413

                            
                        
414
        flashLoansPaused = _paused;
415
        emit FlashLoansPaused(msg.sender, _paused);
416
    }
417
}
418

                            
                        

Lines covered: 247 / 332 (74.4%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IBorrowerOperations.sol";
6
import "./Interfaces/ICdpManager.sol";
7
import "./Interfaces/ICdpManagerData.sol";
8
import "./Interfaces/IEBTCToken.sol";
9
import "./Interfaces/ICollSurplusPool.sol";
10
import "./Interfaces/ISortedCdps.sol";
11
import "./Dependencies/LiquityBase.sol";
12
import "./Dependencies/ReentrancyGuard.sol";
13
import "./Dependencies/Ownable.sol";
14
import "./Dependencies/AuthNoOwner.sol";
15
import "./Dependencies/ERC3156FlashLender.sol";
16
import "./Dependencies/PermitNonce.sol";
17

                            
                        
18
contract BorrowerOperations is
19
    LiquityBase,
20
    ReentrancyGuard,
21
    IBorrowerOperations,
22
    ERC3156FlashLender,
23
    AuthNoOwner,
24
    PermitNonce
25
{
26
    string public constant NAME = "BorrowerOperations";
27

                            
                        
28
    // keccak256("permitPositionManagerApproval(address borrower,address positionManager,uint8 status,uint256 nonce,uint256 deadline)");
29
    bytes32 private constant _PERMIT_POSITION_MANAGER_TYPEHASH =
30
        keccak256(
31
            "PermitPositionManagerApproval(address borrower,address positionManager,uint8 status,uint256 nonce,uint256 deadline)"
32
        );
33

                            
                        
34
    // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
35
    bytes32 private constant _TYPE_HASH =
36
        0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
37

                            
                        
38
    string internal constant _VERSION = "1";
39

                            
                        
40
    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
41
    // invalidate the cached domain separator if the chain id changes.
42
    bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
43
    uint256 private immutable _CACHED_CHAIN_ID;
44

                            
                        
45
    bytes32 private immutable _HASHED_NAME;
46
    bytes32 private immutable _HASHED_VERSION;
47

                            
                        
48
    // --- Connected contract declarations ---
49

                            
                        
50
    ICdpManager public immutable cdpManager;
51

                            
                        
52
    ICollSurplusPool public immutable collSurplusPool;
53

                            
                        
54
    address public feeRecipientAddress;
55

                            
                        
56
    IEBTCToken public immutable ebtcToken;
57

                            
                        
58
    // A doubly linked list of Cdps, sorted by their collateral ratios
59
    ISortedCdps public immutable sortedCdps;
60

                            
                        
61
    // Mapping of borrowers to approved position managers, by approval status: cdpOwner(borrower) -> positionManager -> PositionManagerApproval (None, OneTime, Persistent)
62
    mapping(address => mapping(address => PositionManagerApproval)) public positionManagers;
63

                            
                        
64
    /* --- Variable container structs  ---
65

                            
                        
66
    Used to hold, return and assign variables inside a function, in order to avoid the error:
67
    "CompilerError: Stack too deep". */
68

                            
                        
69
    struct LocalVariables_adjustCdp {
70
        uint256 price;
71
        uint256 collChange;
72
        uint256 netDebtChange;
73
        bool isCollIncrease;
74
        uint256 debt;
75
        uint256 coll;
76
        uint256 oldICR;
77
        uint256 newICR;
78
        uint256 newTCR;
79
        uint256 newDebt;
80
        uint256 newColl;
81
        uint256 stake;
82
    }
83

                            
                        
84
    struct LocalVariables_openCdp {
85
        uint256 price;
86
        uint256 debt;
87
        uint256 totalColl;
88
        uint256 netColl;
89
        uint256 ICR;
90
        uint256 NICR;
91
        uint256 stake;
92
        uint256 arrayIndex;
93
    }
94

                            
                        
95
    struct LocalVariables_moveTokens {
96
        address user;
97
        uint256 collChange;
98
        uint256 collAddUnderlying; // ONLY for isCollIncrease=true
99
        bool isCollIncrease;
100
        uint256 EBTCChange;
101
        bool isDebtIncrease;
102
        uint256 netDebtChange;
103
    }
104

                            
                        
105
    // --- Dependency setters ---
106
    constructor(
107
        address _cdpManagerAddress,
108
        address _activePoolAddress,
109
        address _collSurplusPoolAddress,
110
        address _priceFeedAddress,
111
        address _sortedCdpsAddress,
112
        address _ebtcTokenAddress,
113
        address _feeRecipientAddress,
114
        address _collTokenAddress
115
    ) LiquityBase(_activePoolAddress, _priceFeedAddress, _collTokenAddress) {
116
        // This makes impossible to open a cdp with zero withdrawn EBTC
117
        // TODO: Re-evaluate this
118

                            
                        
119
        cdpManager = ICdpManager(_cdpManagerAddress);
120
        collSurplusPool = ICollSurplusPool(_collSurplusPoolAddress);
121
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
122
        ebtcToken = IEBTCToken(_ebtcTokenAddress);
123
        feeRecipientAddress = _feeRecipientAddress;
124

                            
                        
125
        address _authorityAddress = address(AuthNoOwner(_cdpManagerAddress).authority());
126
        if (_authorityAddress != address(0)) {
127
            _initializeAuthority(_authorityAddress);
128
        }
129

                            
                        
130
        bytes32 hashedName = keccak256(bytes(NAME));
131
        bytes32 hashedVersion = keccak256(bytes(_VERSION));
132

                            
                        
133
        _HASHED_NAME = hashedName;
134
        _HASHED_VERSION = hashedVersion;
135
        _CACHED_CHAIN_ID = _chainID();
136
        _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, hashedName, hashedVersion);
137

                            
                        
138
        emit FeeRecipientAddressChanged(_feeRecipientAddress);
139
    }
140

                            
                        
141
    /**
142
        @notice BorrowerOperations and CdpManager share reentrancy status by confirming the other's locked flag before beginning operation
143
        @dev This is an alternative to the more heavyweight solution of both being able to set the reentrancy flag on a 3rd contract.
144
        @dev Prevents multi-contract reentrancy between these two contracts
145
     */
146
    modifier nonReentrantSelfAndCdpM() {
147
        require(locked == OPEN, "BorrowerOperations: Reentrancy in nonReentrant call");
148
        require(
149
            ReentrancyGuard(address(cdpManager)).locked() == OPEN,
150
            "CdpManager: Reentrancy in nonReentrant call"
151
        );
152

                            
                        
153
        locked = LOCKED;
154

                            
                        
155
        _;
156

                            
                        
157
        locked = OPEN;
158
    }
159

                            
                        
160
    // --- Borrower Cdp Operations ---
161

                            
                        
162
    /**
163
    @notice Function that creates a Cdp for the caller with the requested debt, and the stETH received as collateral.
164
    @notice Successful execution is conditional mainly on the resulting collateralization ratio which must exceed the minimum (110% in Normal Mode, 150% in Recovery Mode).
165
    @notice In addition to the requested debt, extra debt is issued to cover the gas compensation.
166
    */
167
    function openCdp(
168
        uint256 _EBTCAmount,
169
        bytes32 _upperHint,
170
        bytes32 _lowerHint,
171
        uint256 _stEthBalance
172
    ) external override nonReentrantSelfAndCdpM returns (bytes32) {
173
        return _openCdp(_EBTCAmount, _upperHint, _lowerHint, _stEthBalance, msg.sender);
174
    }
175

                            
                        
176
    function openCdpFor(
177
        uint256 _EBTCAmount,
178
        bytes32 _upperHint,
179
        bytes32 _lowerHint,
180
        uint256 _collAmount,
181
        address _borrower
182
    ) external override nonReentrantSelfAndCdpM returns (bytes32) {
183
        return _openCdp(_EBTCAmount, _upperHint, _lowerHint, _collAmount, _borrower);
184
    }
185

                            
                        
186
    // Function that adds the received stETH to the caller's specified Cdp.
187
    function addColl(
188
        bytes32 _cdpId,
189
        bytes32 _upperHint,
190
        bytes32 _lowerHint,
191
        uint256 _stEthBalanceIncrease
192
    ) external override nonReentrantSelfAndCdpM {
193
        _adjustCdpInternal(_cdpId, 0, 0, false, _upperHint, _lowerHint, _stEthBalanceIncrease);
194
    }
195

                            
                        
196
    /**
197
    Withdraws `_stEthBalanceDecrease` amount of collateral from the caller’s Cdp. Executes only if the user has an active Cdp, the withdrawal would not pull the user’s Cdp below the minimum collateralization ratio, and the resulting total collateralization ratio of the system is above 150%.
198
    */
199
    function withdrawColl(
200
        bytes32 _cdpId,
201
        uint256 _stEthBalanceDecrease,
202
        bytes32 _upperHint,
203
        bytes32 _lowerHint
204
    ) external override nonReentrantSelfAndCdpM {
205
        _adjustCdpInternal(_cdpId, _stEthBalanceDecrease, 0, false, _upperHint, _lowerHint, 0);
206
    }
207

                            
                        
208
    // Withdraw EBTC tokens from a cdp: mint new EBTC tokens to the owner, and increase the cdp's debt accordingly
209
    /**
210
    Issues `_amount` of eBTC from the caller’s Cdp to the caller. Executes only if the Cdp's collateralization ratio would remain above the minimum, and the resulting total collateralization ratio is above 150%.
211
     */
212
    function withdrawEBTC(
213
        bytes32 _cdpId,
214
        uint256 _EBTCAmount,
215
        bytes32 _upperHint,
216
        bytes32 _lowerHint
217
    ) external override nonReentrantSelfAndCdpM {
218
        _adjustCdpInternal(_cdpId, 0, _EBTCAmount, true, _upperHint, _lowerHint, 0);
219
    }
220

                            
                        
221
    // Repay EBTC tokens to a Cdp: Burn the repaid EBTC tokens, and reduce the cdp's debt accordingly
222
    /**
223
    repay `_amount` of eBTC to the caller’s Cdp, subject to leaving 50 debt in the Cdp (which corresponds to the 50 eBTC gas compensation).
224
    */
225
    function repayEBTC(
226
        bytes32 _cdpId,
227
        uint256 _EBTCAmount,
228
        bytes32 _upperHint,
229
        bytes32 _lowerHint
230
    ) external override nonReentrantSelfAndCdpM {
231
        _adjustCdpInternal(_cdpId, 0, _EBTCAmount, false, _upperHint, _lowerHint, 0);
232
    }
233

                            
                        
234
    function adjustCdp(
235
        bytes32 _cdpId,
236
        uint256 _stEthBalanceDecrease,
237
        uint256 _EBTCChange,
238
        bool _isDebtIncrease,
239
        bytes32 _upperHint,
240
        bytes32 _lowerHint
241
    ) external override nonReentrantSelfAndCdpM {
242
        _adjustCdpInternal(
243
            _cdpId,
244
            _stEthBalanceDecrease,
245
            _EBTCChange,
246
            _isDebtIncrease,
247
            _upperHint,
248
            _lowerHint,
249
            0
250
        );
251
    }
252

                            
                        
253
    /**
254
    enables a borrower to simultaneously change both their collateral and debt, subject to all the restrictions that apply to individual increases/decreases of each quantity with the following particularity: if the adjustment reduces the collateralization ratio of the Cdp, the function only executes if the resulting total collateralization ratio is above 150%. The borrower has to provide a `_maxFeePercentage` that he/she is willing to accept in case of a fee slippage, i.e. when a redemption transaction is processed first, driving up the issuance fee. The parameter is ignored if the debt is not increased with the transaction.
255
    */
256
    // TODO optimization candidate
257
    function adjustCdpWithColl(
258
        bytes32 _cdpId,
259
        uint256 _stEthBalanceDecrease,
260
        uint256 _EBTCChange,
261
        bool _isDebtIncrease,
262
        bytes32 _upperHint,
263
        bytes32 _lowerHint,
264
        uint256 _stEthBalanceIncrease
265
    ) external override nonReentrantSelfAndCdpM {
266
        _adjustCdpInternal(
267
            _cdpId,
268
            _stEthBalanceDecrease,
269
            _EBTCChange,
270
            _isDebtIncrease,
271
            _upperHint,
272
            _lowerHint,
273
            _stEthBalanceIncrease
274
        );
275
    }
276

                            
                        
277
    /*
278
     * _adjustCdpInternal(): Alongside a debt change, this function can perform either
279
     * a collateral top-up or a collateral withdrawal.
280
     *
281
     * It therefore expects either a positive _stEthBalanceIncrease, or a positive _stEthBalanceDecrease argument.
282
     *
283
     * If both are positive, it will revert.
284
     */
285
    function _adjustCdpInternal(
286
        bytes32 _cdpId,
287
        uint256 _stEthBalanceDecrease,
288
        uint256 _EBTCChange,
289
        bool _isDebtIncrease,
290
        bytes32 _upperHint,
291
        bytes32 _lowerHint,
292
        uint256 _stEthBalanceIncrease
293
    ) internal {
294
        // Confirm the operation is the borrower or approved position manager adjusting its own cdp
295
        address _borrower = sortedCdps.getOwnerAddress(_cdpId);
296
        _requireBorrowerOrPositionManagerAndUpdate(_borrower);
297

                            
                        
298
        _requireCdpisActive(cdpManager, _cdpId);
299

                            
                        
300
        cdpManager.syncAccounting(_cdpId);
301

                            
                        
302
        LocalVariables_adjustCdp memory vars;
303

                            
                        
304
        vars.price = priceFeed.fetchPrice();
305
        bool isRecoveryMode = _checkRecoveryModeForTCR(_getTCR(vars.price));
306

                            
                        
307
        if (_isDebtIncrease) {
308
            _requireNonZeroDebtChange(_EBTCChange);
309
        }
310
        _requireSingularCollChange(_stEthBalanceIncrease, _stEthBalanceDecrease);
311
        _requireNonZeroAdjustment(_stEthBalanceIncrease, _stEthBalanceDecrease, _EBTCChange);
312

                            
                        
313
        // Get the collChange based on the collateral value transferred in the transaction
314
        (vars.collChange, vars.isCollIncrease) = _getCollSharesChangeFromStEthChange(
315
            _stEthBalanceIncrease,
316
            _stEthBalanceDecrease
317
        );
318

                            
                        
319
        vars.netDebtChange = _EBTCChange;
320

                            
                        
321
        vars.debt = cdpManager.getCdpDebt(_cdpId);
322
        vars.coll = cdpManager.getCdpCollShares(_cdpId);
323

                            
                        
324
        // Get the cdp's old ICR before the adjustment, and what its new ICR will be after the adjustment
325
        uint256 _cdpStEthBalance = collateral.getPooledEthByShares(vars.coll);
326
        require(
327
            _stEthBalanceDecrease <= _cdpStEthBalance,
328
            "BorrowerOperations: withdraw more collateral than CDP has!"
329
        );
330
        vars.oldICR = LiquityMath._computeCR(_cdpStEthBalance, vars.debt, vars.price);
331
        vars.newICR = _getNewICRFromCdpChange(
332
            vars.coll,
333
            vars.debt,
334
            vars.collChange,
335
            vars.isCollIncrease,
336
            vars.netDebtChange,
337
            _isDebtIncrease,
338
            vars.price
339
        );
340

                            
                        
341
        // Check the adjustment satisfies all conditions for the current system mode
342
        _requireValidAdjustmentInCurrentMode(
343
            isRecoveryMode,
344
            _stEthBalanceDecrease,
345
            _isDebtIncrease,
346
            vars
347
        );
348

                            
                        
349
        // When the adjustment is a debt repayment, check it's a valid amount, that the caller has enough EBTC, and that the resulting debt is >0
350
        if (!_isDebtIncrease && _EBTCChange > 0) {
351
            _requireValidEBTCRepayment(vars.debt, vars.netDebtChange);
352
            _requireSufficientEBTCBalance(ebtcToken, msg.sender, vars.netDebtChange);
353
            _requireNonZeroDebt(vars.debt - vars.netDebtChange);
354
        }
355

                            
                        
356
        (vars.newColl, vars.newDebt) = _getNewCdpAmounts(
357
            vars.coll,
358
            vars.debt,
359
            vars.collChange,
360
            vars.isCollIncrease,
361
            vars.netDebtChange,
362
            _isDebtIncrease
363
        );
364

                            
                        
365
        _requireAtLeastMinNetStEthBalance(collateral.getPooledEthByShares(vars.newColl));
366

                            
                        
367
        cdpManager.updateCdp(_cdpId, _borrower, vars.coll, vars.debt, vars.newColl, vars.newDebt);
368

                            
                        
369
        // Re-insert cdp in to the sorted list
370
        {
371
            uint256 newNICR = _getNewNominalICRFromCdpChange(vars, _isDebtIncrease);
372
            sortedCdps.reInsert(_cdpId, newNICR, _upperHint, _lowerHint);
373
        }
374

                            
                        
375
        // Use the unmodified _EBTCChange here, as we don't send the fee to the user
376
        {
377
            LocalVariables_moveTokens memory _varMvTokens = LocalVariables_moveTokens(
378
                msg.sender,
379
                vars.collChange,
380
                (vars.isCollIncrease ? _stEthBalanceIncrease : 0),
381
                vars.isCollIncrease,
382
                _EBTCChange,
383
                _isDebtIncrease,
384
                vars.netDebtChange
385
            );
386
            _processTokenMovesFromAdjustment(_varMvTokens);
387
        }
388
    }
389

                            
                        
390
    function _openCdp(
391
        uint256 _EBTCAmount,
392
        bytes32 _upperHint,
393
        bytes32 _lowerHint,
394
        uint256 _stEthBalance,
395
        address _borrower
396
    ) internal returns (bytes32) {
397
        _requireNonZeroDebt(_EBTCAmount);
398
        _requireBorrowerOrPositionManagerAndUpdate(_borrower);
399

                            
                        
400
        LocalVariables_openCdp memory vars;
401

                            
                        
402
        // ICR is based on the net coll, i.e. the requested coll amount - fixed liquidator incentive gas comp.
403
        vars.netColl = _getNetColl(_stEthBalance);
404

                            
                        
405
        // will revert if _stEthBalance is less than MIN_NET_COLL + LIQUIDATOR_REWARD
406
        _requireAtLeastMinNetStEthBalance(vars.netColl);
407

                            
                        
408
        // Update global pending index before any operations
409
        cdpManager.syncGlobalAccounting();
410

                            
                        
411
        vars.price = priceFeed.fetchPrice();
412
        bool isRecoveryMode = _checkRecoveryModeForTCR(_getTCR(vars.price));
413

                            
                        
414
        vars.debt = _EBTCAmount;
415

                            
                        
416
        // Sanity check
417
        require(vars.netColl > 0, "BorrowerOperations: zero collateral for openCdp()!");
418

                            
                        
419
        uint256 _netCollAsShares = collateral.getSharesByPooledEth(vars.netColl);
420
        uint256 _liquidatorRewardShares = collateral.getSharesByPooledEth(LIQUIDATOR_REWARD);
421

                            
                        
422
        // ICR is based on the net coll, i.e. the requested coll amount - fixed liquidator incentive gas comp.
423
        vars.ICR = LiquityMath._computeCR(vars.netColl, vars.debt, vars.price);
424

                            
                        
425
        // NICR uses shares to normalize NICR across CDPs opened at different pooled ETH / shares ratios
426
        vars.NICR = LiquityMath._computeNominalCR(_netCollAsShares, vars.debt);
427

                            
                        
428
        /**
429
            In recovery move, ICR must be greater than CCR
430
            CCR > MCR (125% vs 110%)
431

                            
                        
432
            In normal mode, ICR must be greater thatn MCR
433
            Additionally, the new system TCR after the CDPs addition must be >CCR
434
        */
435
        uint256 newTCR = _getNewTCRFromCdpChange(vars.netColl, true, vars.debt, true, vars.price);
436
        if (isRecoveryMode) {
437
            _requireICRisAboveCCR(vars.ICR);
438

                            
                        
439
            // == Grace Period == //
440
            // We are in RM, Edge case is Depositing Coll could exit RM
441
            // We check with newTCR
442
            if (newTCR < CCR) {
443
                // Notify RM
444
                cdpManager.notifyStartGracePeriod(newTCR);
445
            } else {
446
                // Notify Back to Normal Mode
447
                cdpManager.notifyEndGracePeriod(newTCR);
448
            }
449
        } else {
450
            _requireICRisAboveMCR(vars.ICR);
451
            _requireNewTCRisAboveCCR(newTCR);
452

                            
                        
453
            // == Grace Period == //
454
            // We are not in RM, no edge case, we always stay above RM
455
            // Always Notify Back to Normal Mode
456
            cdpManager.notifyEndGracePeriod(newTCR);
457
        }
458

                            
                        
459
        // Set the cdp struct's properties
460
        bytes32 _cdpId = sortedCdps.insert(_borrower, vars.NICR, _upperHint, _lowerHint);
461

                            
                        
462
        // Collision check: collisions should never occur
463
        // Explicitly prevent it by checking for `nonExistent`
464
        _requireCdpIsNonExistent(_cdpId);
465

                            
                        
466
        // Collateral is stored in shares form for normalization
467
        cdpManager.initializeCdp(
468
            _cdpId,
469
            vars.debt,
470
            _netCollAsShares,
471
            _liquidatorRewardShares,
472
            _borrower
473
        );
474

                            
                        
475
        // Mint the full EBTCAmount to the caller
476
        _withdrawEBTC(msg.sender, _EBTCAmount, _EBTCAmount);
477

                            
                        
478
        /**
479
            Note that only NET coll (as shares) is considered part of the CDP.
480
            The static liqudiation incentive is stored in the gas pool and can be considered a deposit / voucher to be returned upon CDP close, to the closer.
481
            The close can happen from the borrower closing their own CDP, a full liquidation, or a redemption.
482
        */
483

                            
                        
484
        // CEI: Move the net collateral and liquidator gas compensation to the Active Pool. Track only net collateral shares for TCR purposes.
485
        _activePoolAddColl(_stEthBalance, _netCollAsShares);
486

                            
                        
487
        // Invariant check
488
        require(
489
            vars.netColl + LIQUIDATOR_REWARD == _stEthBalance,
490
            "BorrowerOperations: deposited collateral mismatch!"
491
        );
492

                            
                        
493
        return _cdpId;
494
    }
495

                            
                        
496
    /**
497
    allows a borrower to repay all debt, withdraw all their collateral, and close their Cdp. Requires the borrower have a eBTC balance sufficient to repay their cdp's debt, excluding gas compensation - i.e. `(debt - 50)` eBTC.
498
    */
499
    function closeCdp(bytes32 _cdpId) external override {
500
        address _borrower = sortedCdps.getOwnerAddress(_cdpId);
501
        _requireBorrowerOrPositionManagerAndUpdate(_borrower);
502

                            
                        
503
        _requireCdpisActive(cdpManager, _cdpId);
504

                            
                        
505
        cdpManager.syncAccounting(_cdpId);
506

                            
                        
507
        uint256 price = priceFeed.fetchPrice();
508
        _requireNotInRecoveryMode(_getTCR(price));
509

                            
                        
510
        uint256 coll = cdpManager.getCdpCollShares(_cdpId);
511
        uint256 debt = cdpManager.getCdpDebt(_cdpId);
512
        uint256 liquidatorRewardShares = cdpManager.getCdpLiquidatorRewardShares(_cdpId);
513

                            
                        
514
        _requireSufficientEBTCBalance(ebtcToken, msg.sender, debt);
515

                            
                        
516
        uint256 newTCR = _getNewTCRFromCdpChange(
517
            collateral.getPooledEthByShares(coll),
518
            false,
519
            debt,
520
            false,
521
            price
522
        );
523
        _requireNewTCRisAboveCCR(newTCR);
524

                            
                        
525
        // == Grace Period == //
526
        // By definition we are not in RM, notify CDPManager to ensure "Glass is on"
527
        cdpManager.notifyEndGracePeriod(newTCR);
528

                            
                        
529
        cdpManager.removeStake(_cdpId);
530

                            
                        
531
        // We already verified msg.sender is the borrower
532
        cdpManager.closeCdp(_cdpId, msg.sender, debt, coll);
533

                            
                        
534
        // Burn the repaid EBTC from the user's balance
535
        _repayEBTC(msg.sender, debt);
536

                            
                        
537
        // CEI: Send the collateral and liquidator reward shares back to the user
538
        activePool.transferSystemCollSharesAndLiquidatorReward(
539
            msg.sender,
540
            coll,
541
            liquidatorRewardShares
542
        );
543
    }
544

                            
                        
545
    /**
546
     * Claim remaining collateral from a redemption or from a liquidation with ICR > MCR in Recovery Mode
547

                            
                        
548
      when a borrower’s Cdp has been fully redeemed from and closed, or liquidated in Recovery Mode with a collateralization ratio above 110%, this function allows the borrower to claim their stETH collateral surplus that remains in the system (collateral - debt upon redemption; collateral - 110% of the debt upon liquidation).
549
     */
550
    function claimSurplusCollShares() external override {
551
        // send ETH from CollSurplus Pool to owner
552
        collSurplusPool.claimSurplusCollShares(msg.sender);
553
    }
554

                            
                        
555
    /// @notice Returns true if the borrower is allowing position manager to act on their behalf
556
    function getPositionManagerApproval(
557
        address _borrower,
558
        address _positionManager
559
    ) external view override returns (PositionManagerApproval) {
560
        return _getPositionManagerApproval(_borrower, _positionManager);
561
    }
562

                            
                        
563
    function _getPositionManagerApproval(
564
        address _borrower,
565
        address _positionManager
566
    ) internal view returns (PositionManagerApproval) {
567
        return positionManagers[_borrower][_positionManager];
568
    }
569

                            
                        
570
    /// @notice Approve an account to take arbitrary actions on your Cdps.
571
    /// @notice Account managers with 'Persistent' status will be able to take actions indefinitely
572
    /// @notice Account managers with 'OneTIme' status will be able to take a single action on one Cdp. Approval will be automatically revoked after one Cdp-related action.
573
    /// @notice Similar to approving tokens, approving a position manager allows _stealing of all positions_ if given to a malicious account.
574
    function setPositionManagerApproval(
575
        address _positionManager,
576
        PositionManagerApproval _approval
577
    ) external override {
578
        _setPositionManagerApproval(msg.sender, _positionManager, _approval);
579
    }
580

                            
                        
581
    function _setPositionManagerApproval(
582
        address _borrower,
583
        address _positionManager,
584
        PositionManagerApproval _approval
585
    ) internal {
586
        positionManagers[_borrower][_positionManager] = _approval;
587
        emit PositionManagerApprovalSet(_borrower, _positionManager, _approval);
588
    }
589

                            
                        
590
    /// @notice Revoke a position manager from taking further actions on your Cdps
591
    /// @notice Similar to approving tokens, approving a position manager allows _stealing of all positions_ if given to a malicious account.
592
    function revokePositionManagerApproval(address _positionManager) external override {
593
        _setPositionManagerApproval(msg.sender, _positionManager, PositionManagerApproval.None);
594
    }
595

                            
                        
596
    /// @notice Allows recipient of delegation to renounce it
597
    function renouncePositionManagerApproval(address _borrower) external override {
598
        _setPositionManagerApproval(_borrower, msg.sender, PositionManagerApproval.None);
599
    }
600

                            
                        
601
    function DOMAIN_SEPARATOR() external view returns (bytes32) {
602
        return domainSeparator();
603
    }
604

                            
                        
605
    function domainSeparator() public view override returns (bytes32) {
606
        if (_chainID() == _CACHED_CHAIN_ID) {
607
            return _CACHED_DOMAIN_SEPARATOR;
608
        } else {
609
            return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
610
        }
611
    }
612

                            
                        
613
    function _chainID() private view returns (uint256) {
614
        return block.chainid;
615
    }
616

                            
                        
617
    function _buildDomainSeparator(
618
        bytes32 typeHash,
619
        bytes32 name,
620
        bytes32 version
621
    ) private view returns (bytes32) {
622
        return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));
623
    }
624

                            
                        
625
    function version() external pure override returns (string memory) {
626
        return _VERSION;
627
    }
628

                            
                        
629
    function permitTypeHash() external pure override returns (bytes32) {
630
        return _PERMIT_POSITION_MANAGER_TYPEHASH;
631
    }
632

                            
                        
633
    function permitPositionManagerApproval(
634
        address _borrower,
635
        address _positionManager,
636
        PositionManagerApproval _approval,
637
        uint256 _deadline,
638
        uint8 v,
639
        bytes32 r,
640
        bytes32 s
641
    ) external override {
642
        require(_deadline >= block.timestamp, "BorrowerOperations: Position manager permit expired");
643

                            
                        
644
        bytes32 digest = keccak256(
645
            abi.encodePacked(
646
                "\x19\x01",
647
                domainSeparator(),
648
                keccak256(
649
                    abi.encode(
650
                        _PERMIT_POSITION_MANAGER_TYPEHASH,
651
                        _borrower,
652
                        _positionManager,
653
                        _approval,
654
                        _nonces[_borrower]++,
655
                        _deadline
656
                    )
657
                )
658
            )
659
        );
660
        address recoveredAddress = ecrecover(digest, v, r, s);
661
        require(
662
            recoveredAddress != address(0) && recoveredAddress == _borrower,
663
            "BorrowerOperations: Invalid signature"
664
        );
665

                            
                        
666
        _setPositionManagerApproval(_borrower, _positionManager, _approval);
667
    }
668

                            
                        
669
    // --- Helper functions ---
670

                            
                        
671
    function _getCollSharesChangeFromStEthChange(
672
        uint256 _collReceived,
673
        uint256 _requestedCollWithdrawal
674
    ) internal view returns (uint256 collChange, bool isCollIncrease) {
675
        if (_collReceived != 0) {
676
            collChange = collateral.getSharesByPooledEth(_collReceived);
677
            isCollIncrease = true;
678
        } else {
679
            collChange = collateral.getSharesByPooledEth(_requestedCollWithdrawal);
680
        }
681
    }
682

                            
                        
683
    /**
684
        @notice Process the token movements required by a CDP adjustment.
685
        @notice Handles the cases of a debt increase / decrease, and/or a collateral increase / decrease.
686
     */
687
    function _processTokenMovesFromAdjustment(
688
        LocalVariables_moveTokens memory _varMvTokens
689
    ) internal {
690
        // Debt increase: mint change value of new eBTC to user, increment ActivePool eBTC internal accounting
691
        if (_varMvTokens.isDebtIncrease) {
692
            _withdrawEBTC(_varMvTokens.user, _varMvTokens.EBTCChange, _varMvTokens.netDebtChange);
693
        } else {
694
            // Debt decrease: burn change value of eBTC from user, decrement ActivePool eBTC internal accounting
695
            _repayEBTC(_varMvTokens.user, _varMvTokens.EBTCChange);
696
        }
697

                            
                        
698
        if (_varMvTokens.isCollIncrease) {
699
            // Coll increase: send change value of stETH to Active Pool, increment ActivePool stETH internal accounting
700
            _activePoolAddColl(_varMvTokens.collAddUnderlying, _varMvTokens.collChange);
701
        } else {
702
            // Coll decrease: send change value of stETH to user, decrement ActivePool stETH internal accounting
703
            activePool.transferSystemCollShares(_varMvTokens.user, _varMvTokens.collChange);
704
        }
705
    }
706

                            
                        
707
    /// @notice Send stETH to Active Pool and increase its recorded ETH balance
708
    /// @param _stEthBalance total balance of stETH to send, inclusive of coll and liquidatorRewardShares
709
    /// @param _sharesToTrack coll as shares (exclsuive of liquidator reward shares)
710
    /// @dev Liquidator reward shares are not considered as part of the system for CR purposes.
711
    /// @dev These number of liquidator shares associated with each CDP are stored in the CDP, while the actual tokens float in the active pool
712
    function _activePoolAddColl(uint256 _stEthBalance, uint256 _sharesToTrack) internal {
713
        // NOTE: No need for safe transfer if the collateral asset is standard. Make sure this is the case!
714
        collateral.transferFrom(msg.sender, address(activePool), _stEthBalance);
715
        activePool.increaseSystemCollShares(_sharesToTrack);
716
    }
717

                            
                        
718
    // Issue the specified amount of EBTC to _account and increases
719
    // the total active debt
720
    function _withdrawEBTC(
721
        address _account,
722
        uint256 _EBTCAmount,
723
        uint256 _netDebtIncrease
724
    ) internal {
725
        activePool.increaseSystemDebt(_netDebtIncrease);
726
        ebtcToken.mint(_account, _EBTCAmount);
727
    }
728

                            
                        
729
    // Burn the specified amount of EBTC from _account and decreases the total active debt
730
    function _repayEBTC(address _account, uint256 _EBTC) internal {
731
        activePool.decreaseSystemDebt(_EBTC);
732
        ebtcToken.burn(_account, _EBTC);
733
    }
734

                            
                        
735
    // --- 'Require' wrapper functions ---
736

                            
                        
737
    function _requireSingularCollChange(
738
        uint256 _stEthBalanceIncrease,
739
        uint256 _stEthBalanceDecrease
740
    ) internal pure {
741
        require(
742
            _stEthBalanceIncrease == 0 || _stEthBalanceDecrease == 0,
743
            "BorrowerOperations: Cannot add and withdraw collateral in same operation"
744
        );
745
    }
746

                            
                        
747
    function _requireNonZeroAdjustment(
748
        uint256 _stEthBalanceIncrease,
749
        uint256 _EBTCChange,
750
        uint256 _stEthBalanceDecrease
751
    ) internal pure {
752
        require(
753
            _stEthBalanceIncrease != 0 || _stEthBalanceDecrease != 0 || _EBTCChange != 0,
754
            "BorrowerOperations: There must be either a collateral change or a debt change"
755
        );
756
    }
757

                            
                        
758
    function _requireCdpisActive(ICdpManager _cdpManager, bytes32 _cdpId) internal view {
759
        uint256 status = _cdpManager.getCdpStatus(_cdpId);
760
        require(status == 1, "BorrowerOperations: Cdp does not exist or is closed");
761
    }
762

                            
                        
763
    function _requireCdpIsNonExistent(bytes32 _cdpId) internal view {
764
        uint status = cdpManager.getCdpStatus(_cdpId);
765
        require(status == 0, "BorrowerOperations: Cdp is active or has been previously closed");
766
    }
767

                            
                        
768
    function _requireNonZeroDebtChange(uint _EBTCChange) internal pure {
769
        require(_EBTCChange > 0, "BorrowerOperations: Debt increase requires non-zero debtChange");
770
    }
771

                            
                        
772
    function _requireNotInRecoveryMode(uint256 _tcr) internal view {
773
        require(
774
            !_checkRecoveryModeForTCR(_tcr),
775
            "BorrowerOperations: Operation not permitted during Recovery Mode"
776
        );
777
    }
778

                            
                        
779
    function _requireNoStEthBalanceDecrease(uint256 _stEthBalanceDecrease) internal pure {
780
        require(
781
            _stEthBalanceDecrease == 0,
782
            "BorrowerOperations: Collateral withdrawal not permitted Recovery Mode"
783
        );
784
    }
785

                            
                        
786
    function _requireValidAdjustmentInCurrentMode(
787
        bool _isRecoveryMode,
788
        uint256 _stEthBalanceDecrease,
789
        bool _isDebtIncrease,
790
        LocalVariables_adjustCdp memory _vars
791
    ) internal {
792
        /*
793
         *In Recovery Mode, only allow:
794
         *
795
         * - Pure collateral top-up
796
         * - Pure debt repayment
797
         * - Collateral top-up with debt repayment
798
         * - A debt increase combined with a collateral top-up which makes the
799
         * ICR >= 150% and improves the ICR (and by extension improves the TCR).
800
         *
801
         * In Normal Mode, ensure:
802
         *
803
         * - The new ICR is above MCR
804
         * - The adjustment won't pull the TCR below CCR
805
         */
806

                            
                        
807
        _vars.newTCR = _getNewTCRFromCdpChange(
808
            collateral.getPooledEthByShares(_vars.collChange),
809
            _vars.isCollIncrease,
810
            _vars.netDebtChange,
811
            _isDebtIncrease,
812
            _vars.price
813
        );
814

                            
                        
815
        if (_isRecoveryMode) {
816
            _requireNoStEthBalanceDecrease(_stEthBalanceDecrease);
817
            if (_isDebtIncrease) {
818
                _requireICRisAboveCCR(_vars.newICR);
819
                _requireNewICRisAboveOldICR(_vars.newICR, _vars.oldICR);
820
            }
821

                            
                        
822
            // == Grace Period == //
823
            // We are in RM, Edge case is Depositing Coll could exit RM
824
            // We check with newTCR
825
            if (_vars.newTCR < CCR) {
826
                // Notify RM
827
                cdpManager.notifyStartGracePeriod(_vars.newTCR);
828
            } else {
829
                // Notify Back to Normal Mode
830
                cdpManager.notifyEndGracePeriod(_vars.newTCR);
831
            }
832
        } else {
833
            // if Normal Mode
834
            _requireICRisAboveMCR(_vars.newICR);
835
            _requireNewTCRisAboveCCR(_vars.newTCR);
836

                            
                        
837
            // == Grace Period == //
838
            // We are not in RM, no edge case, we always stay above RM
839
            // Always Notify Back to Normal Mode
840
            cdpManager.notifyEndGracePeriod(_vars.newTCR);
841
        }
842
    }
843

                            
                        
844
    function _requireICRisAboveMCR(uint256 _newICR) internal pure {
845
        require(
846
            _newICR >= MCR,
847
            "BorrowerOperations: An operation that would result in ICR < MCR is not permitted"
848
        );
849
    }
850

                            
                        
851
    function _requireICRisAboveCCR(uint256 _newICR) internal pure {
852
        require(_newICR >= CCR, "BorrowerOperations: Operation must leave cdp with ICR >= CCR");
853
    }
854

                            
                        
855
    function _requireNewICRisAboveOldICR(uint256 _newICR, uint256 _oldICR) internal pure {
856
        require(
857
            _newICR >= _oldICR,
858
            "BorrowerOperations: Cannot decrease your Cdp's ICR in Recovery Mode"
859
        );
860
    }
861

                            
                        
862
    function _requireNewTCRisAboveCCR(uint256 _newTCR) internal pure {
863
        require(
864
            _newTCR >= CCR,
865
            "BorrowerOperations: An operation that would result in TCR < CCR is not permitted"
866
        );
867
    }
868

                            
                        
869
    function _requireNonZeroDebt(uint256 _debt) internal pure {
870
        require(_debt > 0, "BorrowerOperations: Debt must be non-zero");
871
    }
872

                            
                        
873
    function _requireAtLeastMinNetStEthBalance(uint256 _coll) internal pure {
874
        require(
875
            _coll >= MIN_NET_COLL,
876
            "BorrowerOperations: Cdp's net coll must be greater than minimum"
877
        );
878
    }
879

                            
                        
880
    function _requireValidEBTCRepayment(uint256 _currentDebt, uint256 _debtRepayment) internal pure {
881
        require(
882
            _debtRepayment <= _currentDebt,
883
            "BorrowerOperations: Amount repaid must not be larger than the Cdp's debt"
884
        );
885
    }
886

                            
                        
887
    function _requireSufficientEBTCBalance(
888
        IEBTCToken _ebtcToken,
889
        address _account,
890
        uint256 _debtRepayment
891
    ) internal view {
892
        require(
893
            _ebtcToken.balanceOf(_account) >= _debtRepayment,
894
            "BorrowerOperations: Caller doesnt have enough EBTC to make repayment"
895
        );
896
    }
897

                            
                        
898
    function _requireBorrowerOrPositionManagerAndUpdate(address _borrower) internal {
899
        if (_borrower == msg.sender) {
900
            return; // Early return, no delegation
901
        }
902

                            
                        
903
        PositionManagerApproval _approval = _getPositionManagerApproval(_borrower, msg.sender);
904
        // Must be an approved position manager at this point
905
        require(
906
            _approval != PositionManagerApproval.None,
907
            "BorrowerOperations: Only borrower account or approved position manager can OpenCdp on borrower's behalf"
908
        );
909

                            
                        
910
        // Conditional Adjustment
911
        /// @dev If this is a position manager operation with a one-time approval, clear that approval
912
        /// @dev If the PositionManagerApproval was none, we should have failed with the check in _requireBorrowerOrPositionManagerAndUpdate
913
        if (_approval == PositionManagerApproval.OneTime) {
914
            _setPositionManagerApproval(_borrower, msg.sender, PositionManagerApproval.None);
915
        }
916
    }
917

                            
                        
918
    // --- ICR and TCR getters ---
919

                            
                        
920
    // Compute the new collateral ratio, considering the change in coll and debt. Assumes 0 pending rewards.
921
    function _getNewNominalICRFromCdpChange(
922
        LocalVariables_adjustCdp memory vars,
923
        bool _isDebtIncrease
924
    ) internal pure returns (uint256) {
925
        (uint256 newColl, uint256 newDebt) = _getNewCdpAmounts(
926
            vars.coll,
927
            vars.debt,
928
            vars.collChange,
929
            vars.isCollIncrease,
930
            vars.netDebtChange,
931
            _isDebtIncrease
932
        );
933

                            
                        
934
        uint256 newNICR = LiquityMath._computeNominalCR(newColl, newDebt);
935
        return newNICR;
936
    }
937

                            
                        
938
    // Compute the new collateral ratio, considering the change in coll and debt. Assumes 0 pending rewards.
939
    function _getNewICRFromCdpChange(
940
        uint256 _coll,
941
        uint256 _debt,
942
        uint256 _collChange,
943
        bool _isCollIncrease,
944
        uint256 _debtChange,
945
        bool _isDebtIncrease,
946
        uint256 _price
947
    ) internal view returns (uint256) {
948
        (uint256 newColl, uint256 newDebt) = _getNewCdpAmounts(
949
            _coll,
950
            _debt,
951
            _collChange,
952
            _isCollIncrease,
953
            _debtChange,
954
            _isDebtIncrease
955
        );
956

                            
                        
957
        uint256 newICR = LiquityMath._computeCR(
958
            collateral.getPooledEthByShares(newColl),
959
            newDebt,
960
            _price
961
        );
962
        return newICR;
963
    }
964

                            
                        
965
    function _getNewCdpAmounts(
966
        uint256 _coll,
967
        uint256 _debt,
968
        uint256 _collChange,
969
        bool _isCollIncrease,
970
        uint256 _debtChange,
971
        bool _isDebtIncrease
972
    ) internal pure returns (uint256, uint256) {
973
        uint256 newColl = _coll;
974
        uint256 newDebt = _debt;
975

                            
                        
976
        newColl = _isCollIncrease ? _coll + _collChange : _coll - _collChange;
977
        newDebt = _isDebtIncrease ? _debt + _debtChange : _debt - _debtChange;
978

                            
                        
979
        return (newColl, newDebt);
980
    }
981

                            
                        
982
    function _getNewTCRFromCdpChange(
983
        uint256 _collChange,
984
        bool _isCollIncrease,
985
        uint256 _debtChange,
986
        bool _isDebtIncrease,
987
        uint256 _price
988
    ) internal view returns (uint256) {
989
        uint256 _shareColl = getSystemCollShares();
990
        uint256 totalColl = collateral.getPooledEthByShares(_shareColl);
991
        uint256 totalDebt = _getSystemDebt();
992

                            
                        
993
        totalColl = _isCollIncrease ? totalColl + _collChange : totalColl - _collChange;
994
        totalDebt = _isDebtIncrease ? totalDebt + _debtChange : totalDebt - _debtChange;
995

                            
                        
996
        uint256 newTCR = LiquityMath._computeCR(totalColl, totalDebt, _price);
997
        return newTCR;
998
    }
999

                            
                        
1000
    // === Flash Loans === //
1001
    function flashLoan(
1002
        IERC3156FlashBorrower receiver,
1003
        address token,
1004
        uint256 amount,
1005
        bytes calldata data
1006
    ) external override returns (bool) {
1007
        require(amount > 0, "BorrowerOperations: 0 Amount");
1008
        uint256 fee = flashFee(token, amount); // NOTE: Check for `eBTCToken` is implicit here // NOTE: Pause check is here
1009
        require(amount <= maxFlashLoan(token), "BorrowerOperations: Too much");
1010

                            
                        
1011
        // Issue EBTC
1012
        ebtcToken.mint(address(receiver), amount);
1013

                            
                        
1014
        // Callback
1015
        require(
1016
            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == FLASH_SUCCESS_VALUE,
1017
            "IERC3156: Callback failed"
1018
        );
1019

                            
                        
1020
        // Gas: Repay from user balance, so we don't trigger a new SSTORE
1021
        // Safe to use transferFrom and unchecked as it's a standard token
1022
        // Also saves gas
1023
        // Send both fee and amount to FEE_RECIPIENT, to burn allowance per EIP-3156
1024
        ebtcToken.transferFrom(address(receiver), feeRecipientAddress, fee + amount);
1025

                            
                        
1026
        // Burn amount, from FEE_RECIPIENT
1027
        ebtcToken.burn(feeRecipientAddress, amount);
1028

                            
                        
1029
        emit FlashLoanSuccess(address(receiver), token, amount, fee);
1030

                            
                        
1031
        return true;
1032
    }
1033

                            
                        
1034
    function flashFee(address token, uint256 amount) public view override returns (uint256) {
1035
        require(token == address(ebtcToken), "BorrowerOperations: EBTC Only");
1036
        require(!flashLoansPaused, "BorrowerOperations: Flash Loans Paused");
1037

                            
                        
1038
        return (amount * feeBps) / MAX_BPS;
1039
    }
1040

                            
                        
1041
    /// @dev Max flashloan, exclusively in ETH equals to the current balance
1042
    function maxFlashLoan(address token) public view override returns (uint256) {
1043
        if (token != address(ebtcToken)) {
1044
            return 0;
1045
        }
1046

                            
                        
1047
        if (flashLoansPaused) {
1048
            return 0;
1049
        }
1050

                            
                        
1051
        return type(uint112).max;
1052
    }
1053

                            
                        
1054
    // === Governed Functions ==
1055

                            
                        
1056
    function setFeeRecipientAddress(address _feeRecipientAddress) external requiresAuth {
1057
        require(
1058
            _feeRecipientAddress != address(0),
1059
            "BorrowerOperations: Cannot set feeRecipient to zero address"
1060
        );
1061

                            
                        
1062
        cdpManager.syncGlobalAccounting();
1063

                            
                        
1064
        feeRecipientAddress = _feeRecipientAddress;
1065
        emit FeeRecipientAddressChanged(_feeRecipientAddress);
1066
    }
1067

                            
                        
1068
    function setFeeBps(uint256 _newFee) external requiresAuth {
1069
        require(_newFee <= MAX_FEE_BPS, "ERC3156FlashLender: _newFee should <= MAX_FEE_BPS");
1070

                            
                        
1071
        cdpManager.syncGlobalAccounting();
1072

                            
                        
1073
        // set new flash fee
1074
        uint256 _oldFee = feeBps;
1075
        feeBps = uint16(_newFee);
1076
        emit FlashFeeSet(msg.sender, _oldFee, _newFee);
1077
    }
1078

                            
                        
1079
    function setFlashLoansPaused(bool _paused) external requiresAuth {
1080
        cdpManager.syncGlobalAccounting();
1081

                            
                        
1082
        flashLoansPaused = _paused;
1083
        emit FlashLoansPaused(msg.sender, _paused);
1084
    }
1085
}
1086

                            
                        

Lines covered: 49 / 58 (84.5%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IPriceFeed.sol";
6
import "./Interfaces/ICdpManager.sol";
7

                            
                        
8
/// @notice The contract allows to check real CR of CDPs
9
///   Acknowledgement: https://github.com/Uniswap/v3-periphery/blob/main/contracts/lens/Quoter.sol
10
contract CRLens {
11
    ICdpManager public immutable cdpManager;
12
    IPriceFeed public immutable priceFeed;
13

                            
                        
14
    constructor(address _cdpManager, address _priceFeed) {
15
        cdpManager = ICdpManager(_cdpManager);
16
        priceFeed = IPriceFeed(_priceFeed);
17
    }
18

                            
                        
19
    // == CORE FUNCTIONS == //
20

                            
                        
21
    /// @notice Returns the TCR of the system after the fee split
22
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
23
    function getRealTCR(bool revertValue) external returns (uint256) {
24
        // Synch State
25
        cdpManager.syncGlobalAccountingAndGracePeriod();
26

                            
                        
27
        // Return latest
28
        uint price = priceFeed.fetchPrice();
29
        uint256 tcr = cdpManager.getTCR(price);
30

                            
                        
31
        if (revertValue) {
32
            assembly {
33
                let ptr := mload(0x40)
34
                mstore(ptr, tcr)
35
                revert(ptr, 32)
36
            }
37
        }
38

                            
                        
39
        return tcr;
40
    }
41

                            
                        
42
    /// @notice Return the ICR of a CDP after the fee split
43
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
44
    function getRealICR(bytes32 cdpId, bool revertValue) external returns (uint256) {
45
        cdpManager.syncAccounting(cdpId);
46
        uint price = priceFeed.fetchPrice();
47
        uint256 icr = cdpManager.getICR(cdpId, price);
48

                            
                        
49
        if (revertValue) {
50
            assembly {
51
                let ptr := mload(0x40)
52
                mstore(ptr, icr)
53
                revert(ptr, 32)
54
            }
55
        }
56

                            
                        
57
        return icr;
58
    }
59

                            
                        
60
    /// @notice Return the ICR of a CDP after the fee split
61
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
62
    function getRealNICR(bytes32 cdpId, bool revertValue) external returns (uint256) {
63
        cdpManager.syncAccounting(cdpId);
64
        uint price = priceFeed.fetchPrice();
65
        uint256 icr = cdpManager.getNominalICR(cdpId);
66

                            
                        
67
        if (revertValue) {
68
            assembly {
69
                let ptr := mload(0x40)
70
                mstore(ptr, icr)
71
                revert(ptr, 32)
72
            }
73
        }
74

                            
                        
75
        return icr;
76
    }
77

                            
                        
78
    /// @dev Returns 1 if we're in RM
79
    function getCheckRecoveryMode(bool revertValue) external returns (uint256) {
80
        // Synch State
81
        cdpManager.syncGlobalAccountingAndGracePeriod();
82

                            
                        
83
        // Return latest
84
        uint price = priceFeed.fetchPrice();
85
        uint256 isRm = cdpManager.checkRecoveryMode(price) == true ? 1 : 0;
86

                            
                        
87
        if (revertValue) {
88
            assembly {
89
                let ptr := mload(0x40)
90
                mstore(ptr, isRm)
91
                revert(ptr, 32)
92
            }
93
        }
94

                            
                        
95
        return isRm;
96
    }
97

                            
                        
98
    // == REVERT LOGIC == //
99
    // Thanks to: https://github.com/Uniswap/v3-periphery/blob/main/contracts/lens/Quoter.sol
100
    // NOTE: You should never use these in prod, these are just for testing //
101

                            
                        
102
    function parseRevertReason(bytes memory reason) private pure returns (uint256) {
103
        if (reason.length != 32) {
104
            if (reason.length < 68) revert("Unexpected error");
105
            assembly {
106
                reason := add(reason, 0x04)
107
            }
108
            revert(abi.decode(reason, (string)));
109
        }
110
        return abi.decode(reason, (uint256));
111
    }
112

                            
                        
113
    /// @notice Returns the TCR of the system after the fee split
114
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
115
    ///     These cost more gas, there should never be a reason for you to use them beside integration with Echidna
116
    function quoteRealTCR() external returns (uint256) {
117
        try this.getRealTCR(true) {} catch (bytes memory reason) {
118
            return parseRevertReason(reason);
119
        }
120
    }
121

                            
                        
122
    /// @notice Returns the ICR of the system after the fee split
123
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
124
    ///     These cost more gas, there should never be a reason for you to use them beside integration with Echidna
125
    function quoteRealICR(bytes32 cdpId) external returns (uint256) {
126
        try this.getRealICR(cdpId, true) {} catch (bytes memory reason) {
127
            return parseRevertReason(reason);
128
        }
129
    }
130

                            
                        
131
    /// @notice Returns the NICR of the system after the fee split
132
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
133
    ///     These cost more gas, there should never be a reason for you to use them beside integration with Echidna
134
    function quoteRealNICR(bytes32 cdpId) external returns (uint256) {
135
        try this.getRealNICR(cdpId, true) {} catch (bytes memory reason) {
136
            return parseRevertReason(reason);
137
        }
138
    }
139

                            
                        
140
    /// @notice Returns whether the system is in RM after taking fee split
141
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
142
    ///     These cost more gas, there should never be a reason for you to use them beside integration with Echidna
143
    function quoteCheckRecoveryMode() external returns (uint256) {
144
        try this.getCheckRecoveryMode(true) {} catch (bytes memory reason) {
145
            return parseRevertReason(reason);
146
        }
147
    }
148
}
149

                            
                        

Lines covered: 286 / 331 (86.4%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ICdpManager.sol";
6
import "./Interfaces/ICollSurplusPool.sol";
7
import "./Interfaces/IEBTCToken.sol";
8
import "./Interfaces/ISortedCdps.sol";
9
import "./Dependencies/ICollateralTokenOracle.sol";
10
import "./CdpManagerStorage.sol";
11
import "./EBTCDeployer.sol";
12
import "./Dependencies/Proxy.sol";
13

                            
                        
14
contract CdpManager is CdpManagerStorage, ICdpManager, Proxy {
15
    // --- Dependency setter ---
16

                            
                        
17
    /**
18
     * @notice Constructor for CdpManager contract.
19
     * @dev Sets up dependencies and initial staking reward split.
20
     * @param _liquidationLibraryAddress Address of the liquidation library.
21
     * @param _authorityAddress Address of the authority.
22
     * @param _borrowerOperationsAddress Address of BorrowerOperations.
23
     * @param _collSurplusPoolAddress Address of CollSurplusPool.
24
     * @param _ebtcTokenAddress Address of the eBTC token.
25
     * @param _sortedCdpsAddress Address of the SortedCDPs.
26
     * @param _activePoolAddress Address of the ActivePool.
27
     * @param _priceFeedAddress Address of the price feed.
28
     * @param _collTokenAddress Address of the collateral token.
29
     */
30
    constructor(
31
        address _liquidationLibraryAddress,
32
        address _authorityAddress,
33
        address _borrowerOperationsAddress,
34
        address _collSurplusPoolAddress,
35
        address _ebtcTokenAddress,
36
        address _sortedCdpsAddress,
37
        address _activePoolAddress,
38
        address _priceFeedAddress,
39
        address _collTokenAddress
40
    )
41
        CdpManagerStorage(
42
            _liquidationLibraryAddress,
43
            _authorityAddress,
44
            _borrowerOperationsAddress,
45
            _collSurplusPoolAddress,
46
            _ebtcTokenAddress,
47
            _sortedCdpsAddress,
48
            _activePoolAddress,
49
            _priceFeedAddress,
50
            _collTokenAddress
51
        )
52
    {
53
        stakingRewardSplit = STAKING_REWARD_SPLIT;
54
        // Emit initial value for analytics
55
        emit StakingRewardSplitSet(stakingRewardSplit);
56

                            
                        
57
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
58
        _syncStEthIndex(_oldIndex, _newIndex);
59
        systemStEthFeePerUnitIndex = DECIMAL_PRECISION;
60
    }
61

                            
                        
62
    // --- Getters ---
63

                            
                        
64
    /**
65
     * @notice Get the count of CDPs in the system
66
     * @return The number of CDPs.
67
     */
68

                            
                        
69
    function getActiveCdpsCount() external view override returns (uint256) {
70
        return CdpIds.length;
71
    }
72

                            
                        
73
    /**
74
     * @notice Get the CdpId at a given index in the CdpIds array.
75
     * @param _index Index of the CdpIds array.
76
     * @return CDP ID.
77
     */
78
    function getIdFromCdpIdsArray(uint256 _index) external view override returns (bytes32) {
79
        return CdpIds[_index];
80
    }
81

                            
                        
82
    // --- Cdp Liquidation functions ---
83
    // -----------------------------------------------------------------
84
    //    CDP ICR     |       Liquidation Behavior (TODO gas compensation?)
85
    //
86
    //  < MCR         |  debt could be fully repaid by liquidator
87
    //                |  and ALL collateral transferred to liquidator
88
    //                |  OR debt could be partially repaid by liquidator and
89
    //                |  liquidator could get collateral of (repaidDebt * max(LICR, min(ICR, MCR)) / price)
90
    //
91
    //  > MCR & < TCR |  only liquidatable in Recovery Mode (TCR < CCR)
92
    //                |  debt could be fully repaid by liquidator
93
    //                |  and up to (repaid debt * MCR) worth of collateral
94
    //                |  transferred to liquidator while the residue of collateral
95
    //                |  will be available in CollSurplusPool for owner to claim
96
    //                |  OR debt could be partially repaid by liquidator and
97
    //                |  liquidator could get collateral of (repaidDebt * max(LICR, min(ICR, MCR)) / price)
98
    // -----------------------------------------------------------------
99

                            
                        
100
    /// @notice Fully liquidate a single CDP by ID. CDP must meet the criteria for liquidation at the time of execution.
101
    /// @notice callable by anyone, attempts to liquidate the CdpId. Executes successfully if Cdp meets the conditions for liquidation (e.g. in Normal Mode, it liquidates if the Cdp's ICR < the system MCR).
102
    /// @dev forwards msg.data directly to the liquidation library using OZ proxy core delegation function
103
    /// @param _cdpId ID of the CDP to liquidate.
104

                            
                        
105
    function liquidate(bytes32 _cdpId) external override {
106
        _delegate(liquidationLibrary);
107
    }
108

                            
                        
109
    /// @notice Partially liquidate a single CDP.
110
    /// @dev forwards msg.data directly to the liquidation library using OZ proxy core delegation function
111
    /// @param _cdpId ID of the CDP to partially liquidate.
112
    /// @param _partialAmount Amount to partially liquidate.
113
    /// @param _upperPartialHint Upper hint for reinsertion of the CDP into the linked list.
114
    /// @param _lowerPartialHint Lower hint for reinsertion of the CDP into the linked list.
115
    function partiallyLiquidate(
116
        bytes32 _cdpId,
117
        uint256 _partialAmount,
118
        bytes32 _upperPartialHint,
119
        bytes32 _lowerPartialHint
120
    ) external override {
121
        _delegate(liquidationLibrary);
122
    }
123

                            
                        
124
    // --- Batch/Sequence liquidation functions ---
125

                            
                        
126
    /// @notice Attempt to liquidate a custom list of CDPs provided by the caller
127
    /// @notice Callable by anyone, accepts a custom list of Cdps addresses as an argument. Steps through the provided list and attempts to liquidate every Cdp, until it reaches the end or it runs out of gas. A Cdp is liquidated only if it meets the conditions for liquidation. For a batch of 10 Cdps, the gas costs per liquidated Cdp are roughly between 75K-83K, for a batch of 50 Cdps between 54K-69K.
128
    /// @dev forwards msg.data directly to the liquidation library using OZ proxy core delegation function
129
    /// @param _cdpArray Array of CDPs to liquidate.
130
    function batchLiquidateCdps(bytes32[] memory _cdpArray) external override {
131
        _delegate(liquidationLibrary);
132
    }
133

                            
                        
134
    // --- Redemption functions ---
135

                            
                        
136
    /// @notice // Redeem as much collateral as possible from given Cdp in exchange for EBTC up to specified maximum
137
    /// @param _redeemColFromCdp Struct containing variables for redeeming collateral.
138
    /// @return singleRedemption Struct containing redemption values.
139
    function _redeemCollateralFromCdp(
140
        SingleRedemptionInputs memory _redeemColFromCdp
141
    ) internal returns (SingleRedemptionValues memory singleRedemption) {
142
        // Determine the remaining amount (lot) to be redeemed,
143
        // capped by the entire debt of the Cdp minus the liquidation reserve
144
        singleRedemption.eBtcToRedeem = LiquityMath._min(
145
            _redeemColFromCdp.maxEBTCamount,
146
            Cdps[_redeemColFromCdp.cdpId].debt /// @audit Redeem everything
147
        );
148

                            
                        
149
        // Get the stEthToRecieve of equivalent value in USD
150
        singleRedemption.stEthToRecieve = collateral.getSharesByPooledEth(
151
            (singleRedemption.eBtcToRedeem * DECIMAL_PRECISION) / _redeemColFromCdp.price
152
        );
153

                            
                        
154
        // Repurposing this struct here to avoid stack too deep.
155
        CdpDebtAndCollShares memory _oldDebtAndColl = CdpDebtAndCollShares(
156
            Cdps[_redeemColFromCdp.cdpId].debt,
157
            Cdps[_redeemColFromCdp.cdpId].coll,
158
            0
159
        );
160

                            
                        
161
        // Decrease the debt and collateral of the current Cdp according to the EBTC lot and corresponding ETH to send
162
        uint256 newDebt = _oldDebtAndColl.entireDebt - singleRedemption.eBtcToRedeem;
163
        uint256 newColl = _oldDebtAndColl.entireColl - singleRedemption.stEthToRecieve; /// @audit This will revert
164

                            
                        
165
        if (newDebt == 0) {
166
            // No debt remains, close CDP
167
            // No debt left in the Cdp, therefore the cdp gets closed
168
            {
169
                address _borrower = sortedCdps.getOwnerAddress(_redeemColFromCdp.cdpId);
170
                uint256 _liquidatorRewardShares = Cdps[_redeemColFromCdp.cdpId]
171
                    .liquidatorRewardShares;
172

                            
                        
173
                singleRedemption.collSurplus = newColl; // Collateral surplus processed on full redemption
174
                singleRedemption.liquidatorRewardShares = _liquidatorRewardShares;
175
                singleRedemption.fullRedemption = true;
176

                            
                        
177
                _closeCdpByRedemption(
178
                    _redeemColFromCdp.cdpId,
179
                    0,
180
                    newColl,
181
                    _liquidatorRewardShares,
182
                    _borrower
183
                );
184

                            
                        
185
                emit CdpUpdated(
186
                    _redeemColFromCdp.cdpId,
187
                    _borrower,
188
                    _oldDebtAndColl.entireDebt,
189
                    _oldDebtAndColl.entireColl,
190
                    0,
191
                    0,
192
                    0,
193
                    CdpOperation.redeemCollateral
194
                );
195
            }
196
        } else {
197
            // Debt remains, reinsert CDP
198
            uint256 newNICR = LiquityMath._computeNominalCR(newColl, newDebt);
199

                            
                        
200
            /*
201
             * If the provided hint is out of date, we bail since trying to reinsert without a good hint will almost
202
             * certainly result in running out of gas.
203
             *
204
             * If the resultant net coll of the partial is less than the minimum, we bail.
205
             */
206
            if (
207
                newNICR != _redeemColFromCdp.partialRedemptionHintNICR ||
208
                collateral.getPooledEthByShares(newColl) < MIN_NET_COLL
209
            ) {
210
                singleRedemption.cancelledPartial = true;
211
                return singleRedemption;
212
            }
213

                            
                        
214
            sortedCdps.reInsert(
215
                _redeemColFromCdp.cdpId,
216
                newNICR,
217
                _redeemColFromCdp.upperPartialRedemptionHint,
218
                _redeemColFromCdp.lowerPartialRedemptionHint
219
            );
220

                            
                        
221
            Cdps[_redeemColFromCdp.cdpId].debt = newDebt;
222
            Cdps[_redeemColFromCdp.cdpId].coll = newColl;
223
            _updateStakeAndTotalStakes(_redeemColFromCdp.cdpId);
224

                            
                        
225
            address _borrower = ISortedCdps(sortedCdps).getOwnerAddress(_redeemColFromCdp.cdpId);
226
            emit CdpUpdated(
227
                _redeemColFromCdp.cdpId,
228
                _borrower,
229
                _oldDebtAndColl.entireDebt,
230
                _oldDebtAndColl.entireColl,
231
                newDebt,
232
                newColl,
233
                Cdps[_redeemColFromCdp.cdpId].stake,
234
                CdpOperation.redeemCollateral
235
            );
236
        }
237

                            
                        
238
        return singleRedemption;
239
    }
240

                            
                        
241
    /*
242
     * Called when a full redemption occurs, and closes the cdp.
243
     * The redeemer swaps (debt) EBTC for (debt)
244
     * worth of stETH, so the stETH liquidation reserve is all that remains.
245
     * In order to close the cdp, the stETH liquidation reserve is returned to the CDP owner,
246
     * The debt recorded on the cdp's struct is zero'd elswhere, in _closeCdp.
247
     * Any surplus stETH left in the cdp, is sent to the Coll surplus pool, and can be later claimed by the borrower.
248
     */
249
    function _closeCdpByRedemption(
250
        bytes32 _cdpId, // TODO: Remove?
251
        uint256 _EBTC,
252
        uint256 _collSurplus,
253
        uint256 _liquidatorRewardShares,
254
        address _borrower
255
    ) internal {
256
        _removeStake(_cdpId);
257
        _closeCdpWithoutRemovingSortedCdps(_cdpId, Status.closedByRedemption);
258

                            
                        
259
        // Update Active Pool EBTC, and send ETH to account
260
        activePool.decreaseSystemDebt(_EBTC);
261

                            
                        
262
        // Register stETH surplus from upcoming transfers of stETH collateral and liquidator reward shares
263
        collSurplusPool.increaseSurplusCollShares(_borrower, _collSurplus + _liquidatorRewardShares);
264

                            
                        
265
        // CEI: send stETH coll and liquidator reward shares from Active Pool to CollSurplus Pool
266
        activePool.transferSystemCollSharesAndLiquidatorReward(
267
            address(collSurplusPool),
268
            _collSurplus,
269
            _liquidatorRewardShares
270
        );
271
    }
272

                            
                        
273
    /// @notice Returns true if the CdpId specified is the lowest-ICR Cdp in the linked list that still has MCR > ICR
274
    /// @dev Returns false if the specified CdpId hint is blank
275
    /// @dev Returns false if the specified CdpId hint doesn't exist in the list
276
    /// @dev Returns false if the ICR of the specified CdpId is < MCR
277
    /// @dev Returns true if the specified CdpId is not blank, exists in the list, has an ICR > MCR, and the next lower Cdp in the list is either blank or has an ICR < MCR.
278
    function _isValidFirstRedemptionHint(
279
        bytes32 _firstRedemptionHint,
280
        uint256 _price
281
    ) internal view returns (bool) {
282
        if (
283
            _firstRedemptionHint == sortedCdps.nonExistId() ||
284
            !sortedCdps.contains(_firstRedemptionHint) ||
285
            getSyncedICR(_firstRedemptionHint, _price) < MCR
286
        ) {
287
            return false;
288
        }
289

                            
                        
290
        bytes32 nextCdp = sortedCdps.getNext(_firstRedemptionHint);
291
        return nextCdp == sortedCdps.nonExistId() || getSyncedICR(nextCdp, _price) < MCR;
292
    }
293

                            
                        
294
    /** 
295
    redeems `_EBTCamount` of eBTC for stETH from the system. Decreases the caller’s eBTC balance, and sends them the corresponding amount of stETH. Executes successfully if the caller has sufficient eBTC to redeem. The number of Cdps redeemed from is capped by `_maxIterations`. The borrower has to provide a `_maxFeePercentage` that he/she is willing to accept in case of a fee slippage, i.e. when another redemption transaction is processed first, driving up the redemption fee.
296
    */
297

                            
                        
298
    /* Send _EBTCamount EBTC to the system and redeem the corresponding amount of collateral
299
     * from as many Cdps as are needed to fill the redemption
300
     * request.  Applies pending rewards to a Cdp before reducing its debt and coll.
301
     *
302
     * Note that if _amount is very large, this function can run out of gas, specially if traversed cdps are small.
303
     * This can be easily avoided by
304
     * splitting the total _amount in appropriate chunks and calling the function multiple times.
305
     *
306
     * Param `_maxIterations` can also be provided, so the loop through Cdps is capped
307
     * (if it’s zero, it will be ignored).This makes it easier to
308
     * avoid OOG for the frontend, as only knowing approximately the average cost of an iteration is enough,
309
     * without needing to know the “topology”
310
     * of the cdp list. It also avoids the need to set the cap in stone in the contract,
311
     * nor doing gas calculations, as both gas price and opcode costs can vary.
312
     *
313
     * All Cdps that are redeemed from -- with the likely exception of the last one -- will end up with no debt left,
314
     * therefore they will be closed.
315
     * If the last Cdp does have some remaining debt, it has a finite ICR, and the reinsertion
316
     * could be anywhere in the list, therefore it requires a hint.
317
     * A frontend should use getRedemptionHints() to calculate what the ICR of this Cdp will be after redemption,
318
     * and pass a hint for its position
319
     * in the sortedCdps list along with the ICR value that the hint was found for.
320
     *
321
     * If another transaction modifies the list between calling getRedemptionHints()
322
     * and passing the hints to redeemCollateral(), it is very likely that the last (partially)
323
     * redeemed Cdp would end up with a different ICR than what the hint is for. In this case the
324
     * redemption will stop after the last completely redeemed Cdp and the sender will keep the
325
     * remaining EBTC amount, which they can attempt to redeem later.
326
     */
327
    function redeemCollateral(
328
        uint256 _EBTCamount,
329
        bytes32 _firstRedemptionHint,
330
        bytes32 _upperPartialRedemptionHint,
331
        bytes32 _lowerPartialRedemptionHint,
332
        uint256 _partialRedemptionHintNICR,
333
        uint256 _maxIterations,
334
        uint256 _maxFeePercentage
335
    ) external override nonReentrantSelfAndBOps {
336
        RedemptionTotals memory totals;
337

                            
                        
338
        _requireValidMaxFeePercentage(_maxFeePercentage);
339

                            
                        
340
        _syncGlobalAccounting(); // Apply state, we will syncGracePeriod at end of function
341

                            
                        
342
        totals.price = priceFeed.fetchPrice();
343
        {
344
            (
345
                uint256 tcrAtStart,
346
                uint256 totalCollSharesAtStart,
347
                uint256 totalEBTCSupplyAtStart
348
            ) = _getTCRWithSystemDebtAndCollShares(totals.price);
349
            totals.tcrAtStart = tcrAtStart;
350
            totals.totalCollSharesAtStart = totalCollSharesAtStart;
351
            totals.totalEBTCSupplyAtStart = totalEBTCSupplyAtStart;
352
        }
353

                            
                        
354
        _requireTCRoverMCR(totals.price, totals.tcrAtStart);
355
        _requireAmountGreaterThanZero(_EBTCamount);
356

                            
                        
357
        require(redemptionsPaused == false, "CdpManager: Redemptions Paused");
358

                            
                        
359
        _requireEBTCBalanceCoversRedemptionAndWithinSupply(
360
            ebtcToken,
361
            msg.sender,
362
            _EBTCamount,
363
            totals.totalEBTCSupplyAtStart
364
        );
365

                            
                        
366
        totals.remainingEBTC = _EBTCamount;
367
        address currentBorrower;
368
        bytes32 _cId = _firstRedemptionHint;
369

                            
                        
370
        if (_isValidFirstRedemptionHint(_firstRedemptionHint, totals.price)) {
371
            currentBorrower = sortedCdps.getOwnerAddress(_firstRedemptionHint);
372
        } else {
373
            _cId = sortedCdps.getLast();
374
            currentBorrower = sortedCdps.getOwnerAddress(_cId);
375
            // Find the first cdp with ICR >= MCR
376
            while (currentBorrower != address(0) && getSyncedICR(_cId, totals.price) < MCR) {
377
                _cId = sortedCdps.getPrev(_cId);
378
                currentBorrower = sortedCdps.getOwnerAddress(_cId);
379
            }
380
        }
381

                            
                        
382
        // Loop through the Cdps starting from the one with lowest collateral
383
        // ratio until _amount of EBTC is exchanged for collateral
384
        if (_maxIterations == 0) {
385
            _maxIterations = type(uint256).max;
386
        }
387

                            
                        
388
        bytes32 _firstRedeemed = _cId;
389
        bytes32 _lastRedeemed = _cId;
390
        uint256 _numCdpsFullyRedeemed;
391

                            
                        
392
        /**
393
            Core Redemption Loop
394
        */
395
        while (currentBorrower != address(0) && totals.remainingEBTC > 0 && _maxIterations > 0) {
396
            // Save the address of the Cdp preceding the current one, before potentially modifying the list
397
            {
398
                _syncAccounting(_cId); /// @audit This happens even if the re-insertion doesn't
399

                            
                        
400
                SingleRedemptionInputs memory _redeemColFromCdp = SingleRedemptionInputs(
401
                    _cId,
402
                    totals.remainingEBTC,
403
                    totals.price,
404
                    _upperPartialRedemptionHint,
405
                    _lowerPartialRedemptionHint,
406
                    _partialRedemptionHintNICR
407
                );
408
                SingleRedemptionValues memory singleRedemption = _redeemCollateralFromCdp(
409
                    _redeemColFromCdp
410
                );
411
                // Partial redemption was cancelled (out-of-date hint, or new net debt < minimum),
412
                // therefore we could not redeem from the last Cdp
413
                if (singleRedemption.cancelledPartial) break;
414

                            
                        
415
                totals.totalEBTCToRedeem = totals.totalEBTCToRedeem + singleRedemption.eBtcToRedeem;
416
                totals.totalETHDrawn = totals.totalETHDrawn + singleRedemption.stEthToRecieve;
417
                totals.remainingEBTC = totals.remainingEBTC - singleRedemption.eBtcToRedeem;
418
                totals.totalCollSharesSurplus =
419
                    totals.totalCollSharesSurplus +
420
                    singleRedemption.collSurplus;
421

                            
                        
422
                if (singleRedemption.fullRedemption) {
423
                    _lastRedeemed = _cId;
424
                    _numCdpsFullyRedeemed = _numCdpsFullyRedeemed + 1;
425
                }
426

                            
                        
427
                bytes32 _nextId = sortedCdps.getPrev(_cId);
428
                address nextUserToCheck = sortedCdps.getOwnerAddress(_nextId);
429
                currentBorrower = nextUserToCheck;
430
                _cId = _nextId;
431
            }
432
            _maxIterations--;
433
        }
434
        require(totals.totalETHDrawn > 0, "CdpManager: Unable to redeem any amount");
435

                            
                        
436
        // remove from sortedCdps
437
        if (_numCdpsFullyRedeemed == 1) {
438
            sortedCdps.remove(_firstRedeemed);
439
        } else if (_numCdpsFullyRedeemed > 1) {
440
            bytes32[] memory _toRemoveIds = _getCdpIdsToRemove(
441
                _lastRedeemed,
442
                _numCdpsFullyRedeemed,
443
                _firstRedeemed
444
            );
445
            sortedCdps.batchRemove(_toRemoveIds);
446
        }
447

                            
                        
448
        // Decay the baseRate due to time passed, and then increase it according to the size of this redemption.
449
        // Use the saved total EBTC supply value, from before it was reduced by the redemption.
450
        _updateBaseRateFromRedemption(
451
            totals.totalETHDrawn,
452
            totals.price,
453
            totals.totalEBTCSupplyAtStart
454
        );
455

                            
                        
456
        // Calculate the ETH fee
457
        totals.ETHFee = _getRedemptionFee(totals.totalETHDrawn);
458

                            
                        
459
        _requireUserAcceptsFee(totals.ETHFee, totals.totalETHDrawn, _maxFeePercentage);
460

                            
                        
461
        totals.ETHToSendToRedeemer = totals.totalETHDrawn - totals.ETHFee;
462

                            
                        
463
        _syncGracePeriodForGivenValues(
464
            totals.totalCollSharesAtStart - totals.totalETHDrawn - totals.totalCollSharesSurplus,
465
            totals.totalEBTCSupplyAtStart - totals.totalEBTCToRedeem,
466
            totals.price
467
        );
468

                            
                        
469
        emit Redemption(
470
            _EBTCamount,
471
            totals.totalEBTCToRedeem,
472
            totals.totalETHDrawn,
473
            totals.ETHFee,
474
            msg.sender
475
        );
476

                            
                        
477
        // Burn the total eBTC that is redeemed
478
        ebtcToken.burn(msg.sender, totals.totalEBTCToRedeem);
479

                            
                        
480
        // Update Active Pool eBTC debt internal accounting
481
        activePool.decreaseSystemDebt(totals.totalEBTCToRedeem);
482

                            
                        
483
        // Allocate the stETH fee to the FeeRecipient
484
        activePool.allocateSystemCollSharesToFeeRecipient(totals.ETHFee);
485

                            
                        
486
        // CEI: Send the stETH drawn to the redeemer
487
        activePool.transferSystemCollShares(msg.sender, totals.ETHToSendToRedeemer);
488
    }
489

                            
                        
490
    // --- Helper functions ---
491

                            
                        
492
    function _getCdpIdsToRemove(
493
        bytes32 _start,
494
        uint256 _total,
495
        bytes32 _end
496
    ) internal view returns (bytes32[] memory) {
497
        uint256 _cnt = _total;
498
        bytes32 _id = _start;
499
        bytes32[] memory _toRemoveIds = new bytes32[](_total);
500
        while (_cnt > 0 && _id != bytes32(0)) {
501
            _toRemoveIds[_total - _cnt] = _id;
502
            _cnt = _cnt - 1;
503
            _id = sortedCdps.getNext(_id);
504
        }
505
        require(_toRemoveIds[0] == _start, "CdpManager: batchRemoveSortedCdpIds check start error");
506
        require(
507
            _toRemoveIds[_total - 1] == _end,
508
            "CdpManager: batchRemoveSortedCdpIds check end error"
509
        );
510
        return _toRemoveIds;
511
    }
512

                            
                        
513
    function syncAccounting(bytes32 _cdpId) external override {
514
        // _requireCallerIsBorrowerOperations(); /// @audit Please check this and let us know if opening this creates issues | TODO: See Stermi Partial Liq
515
        return _syncAccounting(_cdpId);
516
    }
517

                            
                        
518
    function removeStake(bytes32 _cdpId) external override {
519
        _requireCallerIsBorrowerOperations();
520
        return _removeStake(_cdpId);
521
    }
522

                            
                        
523
    // get totalStakes after split fee taken removed
524
    function getTotalStakeForFeeTaken(
525
        uint256 _feeTaken
526
    ) public view override returns (uint256, uint256) {
527
        uint256 stake = _computeNewStake(_feeTaken);
528
        uint256 _newTotalStakes = totalStakes - stake;
529
        return (_newTotalStakes, stake);
530
    }
531

                            
                        
532
    function updateStakeAndTotalStakes(bytes32 _cdpId) external override returns (uint256) {
533
        _requireCallerIsBorrowerOperations();
534
        return _updateStakeAndTotalStakes(_cdpId);
535
    }
536

                            
                        
537
    function closeCdp(
538
        bytes32 _cdpId,
539
        address _borrower,
540
        uint256 _debt,
541
        uint256 _coll
542
    ) external override {
543
        _requireCallerIsBorrowerOperations();
544
        emit CdpUpdated(_cdpId, _borrower, _debt, _coll, 0, 0, 0, CdpOperation.closeCdp);
545
        return _closeCdp(_cdpId, Status.closedByOwner);
546
    }
547

                            
                        
548
    // Push the owner's address to the Cdp owners list, and record the corresponding array index on the Cdp struct
549
    function _addCdpIdToArray(bytes32 _cdpId) internal returns (uint128 index) {
550
        /* Max array size is 2**128 - 1, i.e. ~3e30 cdps. No risk of overflow, since cdps have minimum EBTC
551
        debt of liquidation reserve plus MIN_NET_DEBT.
552
        3e30 EBTC dwarfs the value of all wealth in the world ( which is < 1e15 USD). */
553

                            
                        
554
        // Push the Cdpowner to the array
555
        CdpIds.push(_cdpId);
556

                            
                        
557
        // Record the index of the new Cdpowner on their Cdp struct
558
        index = uint128(CdpIds.length - 1);
559
        Cdps[_cdpId].arrayIndex = index;
560

                            
                        
561
        return index;
562
    }
563

                            
                        
564
    // --- Recovery Mode and TCR functions ---
565

                            
                        
566
    /**
567
    Returns the systemic entire debt assigned to Cdps, i.e. the systemDebt value of the Active Pool.
568
     */
569
    function getSystemDebt() public view returns (uint256 entireSystemDebt) {
570
        return _getSystemDebt();
571
    }
572

                            
                        
573
    /**
574
    returns the total collateralization ratio (TCR) of the system.  The TCR is based on the the entire system debt and collateral (including pending rewards). */
575
    function getTCR(uint256 _price) external view override returns (uint256) {
576
        return _getTCR(_price);
577
    }
578

                            
                        
579
    /**
580
    reveals whether or not the system is in Recovery Mode (i.e. whether the Total Collateralization Ratio (TCR) is below the Critical Collateralization Ratio (CCR)).
581
    */
582
    function checkRecoveryMode(uint256 _price) external view override returns (bool) {
583
        return _checkRecoveryMode(_price);
584
    }
585

                            
                        
586
    // Check whether or not the system *would be* in Recovery Mode,
587
    // given an ETH:USD price, and the entire system coll and debt.
588
    function _checkPotentialRecoveryMode(
589
        uint256 _systemCollShares,
590
        uint256 _systemDebt,
591
        uint256 _price
592
    ) internal view returns (bool) {
593
        uint256 TCR = _computeTCRWithGivenSystemValues(_systemCollShares, _systemDebt, _price);
594
        return TCR < CCR;
595
    }
596

                            
                        
597
    // --- Redemption fee functions ---
598

                            
                        
599
    /*
600
     * This function has two impacts on the baseRate state variable:
601
     * 1) decays the baseRate based on time passed since last redemption or EBTC borrowing operation.
602
     * then,
603
     * 2) increases the baseRate based on the amount redeemed, as a proportion of total supply
604
     */
605
    function _updateBaseRateFromRedemption(
606
        uint256 _ETHDrawn,
607
        uint256 _price,
608
        uint256 _totalEBTCSupply
609
    ) internal returns (uint256) {
610
        uint256 decayedBaseRate = _calcDecayedBaseRate();
611

                            
                        
612
        /* Convert the drawn ETH back to EBTC at face value rate (1 EBTC:1 USD), in order to get
613
         * the fraction of total supply that was redeemed at face value. */
614
        uint256 redeemedEBTCFraction = (collateral.getPooledEthByShares(_ETHDrawn) * _price) /
615
            _totalEBTCSupply;
616

                            
                        
617
        uint256 newBaseRate = decayedBaseRate + (redeemedEBTCFraction / beta);
618
        newBaseRate = LiquityMath._min(newBaseRate, DECIMAL_PRECISION); // cap baseRate at a maximum of 100%
619
        require(newBaseRate > 0, "CdpManager: new baseRate is zero!"); // Base rate is always non-zero after redemption
620

                            
                        
621
        // Update the baseRate state variable
622
        baseRate = newBaseRate;
623
        emit BaseRateUpdated(newBaseRate);
624

                            
                        
625
        _updateLastRedemptionTimestamp();
626

                            
                        
627
        return newBaseRate;
628
    }
629

                            
                        
630
    function getRedemptionRate() public view override returns (uint256) {
631
        return _calcRedemptionRate(baseRate);
632
    }
633

                            
                        
634
    function getRedemptionRateWithDecay() public view override returns (uint256) {
635
        return _calcRedemptionRate(_calcDecayedBaseRate());
636
    }
637

                            
                        
638
    function _calcRedemptionRate(uint256 _baseRate) internal view returns (uint256) {
639
        return
640
            LiquityMath._min(
641
                redemptionFeeFloor + _baseRate,
642
                DECIMAL_PRECISION // cap at a maximum of 100%
643
            );
644
    }
645

                            
                        
646
    function _getRedemptionFee(uint256 _ETHDrawn) internal view returns (uint256) {
647
        return _calcRedemptionFee(getRedemptionRate(), _ETHDrawn);
648
    }
649

                            
                        
650
    function getRedemptionFeeWithDecay(uint256 _ETHDrawn) external view override returns (uint256) {
651
        return _calcRedemptionFee(getRedemptionRateWithDecay(), _ETHDrawn);
652
    }
653

                            
                        
654
    function _calcRedemptionFee(
655
        uint256 _redemptionRate,
656
        uint256 _ETHDrawn
657
    ) internal pure returns (uint256) {
658
        uint256 redemptionFee = (_redemptionRate * _ETHDrawn) / DECIMAL_PRECISION;
659
        require(redemptionFee < _ETHDrawn, "CdpManager: Fee would eat up all returned collateral");
660
        return redemptionFee;
661
    }
662

                            
                        
663
    // Updates the baseRate state variable based on time elapsed since the last redemption or EBTC borrowing operation.
664
    function decayBaseRateFromBorrowing() external override {
665
        _requireCallerIsBorrowerOperations();
666

                            
                        
667
        _decayBaseRate();
668
    }
669

                            
                        
670
    function _decayBaseRate() internal {
671
        uint256 decayedBaseRate = _calcDecayedBaseRate();
672
        require(decayedBaseRate <= DECIMAL_PRECISION, "CdpManager: baseRate too large!"); // The baseRate can decay to 0
673

                            
                        
674
        baseRate = decayedBaseRate;
675
        emit BaseRateUpdated(decayedBaseRate);
676

                            
                        
677
        _updateLastRedemptionTimestamp();
678
    }
679

                            
                        
680
    // --- Internal fee functions ---
681

                            
                        
682
    // Update the last fee operation time only if time passed >= decay interval. This prevents base rate griefing.
683
    function _updateLastRedemptionTimestamp() internal {
684
        uint256 timePassed = block.timestamp > lastRedemptionTimestamp
685
            ? block.timestamp - lastRedemptionTimestamp
686
            : 0;
687

                            
                        
688
        if (timePassed >= SECONDS_IN_ONE_MINUTE) {
689
            // Using the effective elapsed time that is consumed so far to update lastRedemptionTimestamp
690
            // instead block.timestamp for consistency with _calcDecayedBaseRate()
691
            lastRedemptionTimestamp += _minutesPassedSinceLastRedemption() * SECONDS_IN_ONE_MINUTE;
692
            emit LastRedemptionTimestampUpdated(block.timestamp);
693
        }
694
    }
695

                            
                        
696
    function _calcDecayedBaseRate() internal view returns (uint256) {
697
        uint256 minutesPassed = _minutesPassedSinceLastRedemption();
698
        uint256 decayFactor = LiquityMath._decPow(minuteDecayFactor, minutesPassed);
699

                            
                        
700
        return (baseRate * decayFactor) / DECIMAL_PRECISION;
701
    }
702

                            
                        
703
    function _minutesPassedSinceLastRedemption() internal view returns (uint256) {
704
        return
705
            block.timestamp > lastRedemptionTimestamp
706
                ? ((block.timestamp - lastRedemptionTimestamp) / SECONDS_IN_ONE_MINUTE)
707
                : 0;
708
    }
709

                            
                        
710
    function getDeploymentStartTime() public view returns (uint256) {
711
        return deploymentStartTime;
712
    }
713

                            
                        
714
    // Check whether or not the system *would be* in Recovery Mode,
715
    // given an ETH:USD price, and the entire system coll and debt.
716
    function checkPotentialRecoveryMode(
717
        uint256 _systemCollShares,
718
        uint256 _systemDebt,
719
        uint256 _price
720
    ) external view returns (bool) {
721
        return _checkPotentialRecoveryMode(_systemCollShares, _systemDebt, _price);
722
    }
723

                            
                        
724
    // --- 'require' wrapper functions ---
725

                            
                        
726
    function _requireEBTCBalanceCoversRedemptionAndWithinSupply(
727
        IEBTCToken _ebtcToken,
728
        address _redeemer,
729
        uint256 _amount,
730
        uint256 _totalSupply
731
    ) internal view {
732
        uint256 callerBalance = _ebtcToken.balanceOf(_redeemer);
733
        require(
734
            callerBalance >= _amount,
735
            "CdpManager: Requested redemption amount must be <= user's EBTC token balance"
736
        );
737
        require(
738
            callerBalance <= _totalSupply,
739
            "CdpManager: redeemer's EBTC balance exceeds total supply!"
740
        );
741
    }
742

                            
                        
743
    function _requireAmountGreaterThanZero(uint256 _amount) internal pure {
744
        require(_amount > 0, "CdpManager: Amount must be greater than zero");
745
    }
746

                            
                        
747
    function _requireTCRoverMCR(uint256 _price, uint256 _TCR) internal view {
748
        require(_TCR >= MCR, "CdpManager: Cannot redeem when TCR < MCR");
749
    }
750

                            
                        
751
    function _requireValidMaxFeePercentage(uint256 _maxFeePercentage) internal view {
752
        require(
753
            _maxFeePercentage >= redemptionFeeFloor && _maxFeePercentage <= DECIMAL_PRECISION,
754
            "Max fee percentage must be between redemption fee floor and 100%"
755
        );
756
    }
757

                            
                        
758
    // --- Governance Parameters ---
759

                            
                        
760
    function setStakingRewardSplit(uint256 _stakingRewardSplit) external requiresAuth {
761
        require(
762
            _stakingRewardSplit <= MAX_REWARD_SPLIT,
763
            "CDPManager: new staking reward split exceeds max"
764
        );
765

                            
                        
766
        syncGlobalAccountingAndGracePeriod();
767

                            
                        
768
        stakingRewardSplit = _stakingRewardSplit;
769
        emit StakingRewardSplitSet(_stakingRewardSplit);
770
    }
771

                            
                        
772
    function setRedemptionFeeFloor(uint256 _redemptionFeeFloor) external requiresAuth {
773
        require(
774
            _redemptionFeeFloor >= MIN_REDEMPTION_FEE_FLOOR,
775
            "CDPManager: new redemption fee floor is lower than minimum"
776
        );
777
        require(
778
            _redemptionFeeFloor <= DECIMAL_PRECISION,
779
            "CDPManager: new redemption fee floor is higher than maximum"
780
        );
781

                            
                        
782
        syncGlobalAccountingAndGracePeriod();
783

                            
                        
784
        redemptionFeeFloor = _redemptionFeeFloor;
785
        emit RedemptionFeeFloorSet(_redemptionFeeFloor);
786
    }
787

                            
                        
788
    function setMinuteDecayFactor(uint256 _minuteDecayFactor) external requiresAuth {
789
        require(
790
            _minuteDecayFactor >= MIN_MINUTE_DECAY_FACTOR,
791
            "CDPManager: new minute decay factor out of range"
792
        );
793
        require(
794
            _minuteDecayFactor <= MAX_MINUTE_DECAY_FACTOR,
795
            "CDPManager: new minute decay factor out of range"
796
        );
797

                            
                        
798
        syncGlobalAccountingAndGracePeriod();
799

                            
                        
800
        // decay first according to previous factor
801
        _decayBaseRate();
802

                            
                        
803
        // set new factor after decaying
804
        minuteDecayFactor = _minuteDecayFactor;
805
        emit MinuteDecayFactorSet(_minuteDecayFactor);
806
    }
807

                            
                        
808
    function setBeta(uint256 _beta) external requiresAuth {
809
        syncGlobalAccountingAndGracePeriod();
810

                            
                        
811
        _decayBaseRate();
812

                            
                        
813
        beta = _beta;
814
        emit BetaSet(_beta);
815
    }
816

                            
                        
817
    function setRedemptionsPaused(bool _paused) external requiresAuth {
818
        syncGlobalAccountingAndGracePeriod();
819
        _decayBaseRate();
820

                            
                        
821
        redemptionsPaused = _paused;
822
        emit RedemptionsPaused(_paused);
823
    }
824

                            
                        
825
    // --- Cdp property getters ---
826

                            
                        
827
    /// @notice Get status of a CDP. Named values can be found in ICdpManagerData.Status.
828
    function getCdpStatus(bytes32 _cdpId) external view override returns (uint256) {
829
        return uint256(Cdps[_cdpId].status);
830
    }
831

                            
                        
832
    /// @notice Get stake value of a CDP.
833
    function getCdpStake(bytes32 _cdpId) external view override returns (uint256) {
834
        return Cdps[_cdpId].stake;
835
    }
836

                            
                        
837
    /// @notice Get stored debt value of a CDP, in eBTC units. Does not include pending changes from redistributions
838
    function getCdpDebt(bytes32 _cdpId) external view override returns (uint256) {
839
        return Cdps[_cdpId].debt;
840
    }
841

                            
                        
842
    /// @notice Get stored collateral value of a CDP, in stETH shares. Does not include pending changes from redistributions or unprocessed staking yield.
843
    function getCdpCollShares(bytes32 _cdpId) external view override returns (uint256) {
844
        return Cdps[_cdpId].coll;
845
    }
846

                            
                        
847
    /**
848
        @notice Get shares value of the liquidator gas incentive reward stored for a CDP. 
849
        @notice This value is processed when a CDP closes. 
850
        @dev This value is returned to the borrower when they close their own CDP
851
        @dev This value is given to liquidators upon fully liquidating a CDP
852
        @dev This value is sent to the CollSurplusPool for reclaiming by the borrower when their CDP is redeemed
853
    */
854
    function getCdpLiquidatorRewardShares(bytes32 _cdpId) external view override returns (uint256) {
855
        return Cdps[_cdpId].liquidatorRewardShares;
856
    }
857

                            
                        
858
    // --- Cdp property setters, called by BorrowerOperations ---
859

                            
                        
860
    /**
861
        @notice Initiailze all state for new CDP
862
        @dev Only callable by BorrowerOperations, critical trust assumption 
863
        @dev Requires CDP to be already inserted into linked list correctly
864
        @param _cdpId id of CDP to initialize state for. Inserting the blank CDP into the linked list grants this ID
865
        @param _debt debt units of CDP
866
        @param _coll collateral shares of CDP
867
        @param _liquidatorRewardShares collateral shares for CDP gas stipend
868
        @param _borrower borrower address
869
     */
870
    function initializeCdp(
871
        bytes32 _cdpId,
872
        uint256 _debt,
873
        uint256 _coll,
874
        uint256 _liquidatorRewardShares,
875
        address _borrower
876
    ) external {
877
        _requireCallerIsBorrowerOperations();
878

                            
                        
879
        Cdps[_cdpId].debt = _debt;
880
        Cdps[_cdpId].coll = _coll;
881
        Cdps[_cdpId].status = Status.active;
882
        Cdps[_cdpId].liquidatorRewardShares = _liquidatorRewardShares;
883

                            
                        
884
        _applyAccumulatedFeeSplit(_cdpId);
885
        _updateRedistributedDebtSnapshot(_cdpId);
886
        uint256 stake = _updateStakeAndTotalStakes(_cdpId);
887
        uint256 index = _addCdpIdToArray(_cdpId);
888

                            
                        
889
        // Previous debt and coll are by definition zero upon opening a new CDP
890
        emit CdpUpdated(_cdpId, _borrower, 0, 0, _debt, _coll, stake, CdpOperation.openCdp);
891
    }
892

                            
                        
893
    /**
894
        @notice Set new CDP debt and collateral values, updating stake accordingly.
895
        @dev Only callable by BorrowerOperations, critical trust assumption 
896
        @param _cdpId Id of CDP to update state for
897
        @param _borrower borrower of CDP. Passed along in function to avoid an extra storage read.
898
        @param _coll collateral shares of CDP before update operation. Passed in function to avoid an extra stroage read.
899
        @param _debt debt units of CDP before update operation. Passed in function to avoid an extra stroage read.
900
        @param _newColl collateral shares of CDP after update operation.
901
        @param _newDebt debt units of CDP after update operation.
902
     */
903
    function updateCdp(
904
        bytes32 _cdpId,
905
        address _borrower,
906
        uint256 _coll,
907
        uint256 _debt,
908
        uint256 _newColl,
909
        uint256 _newDebt
910
    ) external {
911
        _requireCallerIsBorrowerOperations();
912

                            
                        
913
        _setCdpCollShares(_cdpId, _newColl);
914
        _setCdpDebt(_cdpId, _newDebt);
915

                            
                        
916
        uint256 stake = _updateStakeAndTotalStakes(_cdpId);
917

                            
                        
918
        emit CdpUpdated(
919
            _cdpId,
920
            _borrower,
921
            _debt,
922
            _coll,
923
            _newDebt,
924
            _newColl,
925
            stake,
926
            CdpOperation.adjustCdp
927
        );
928
    }
929

                            
                        
930
    /**
931
     * @notice Set the collateral of a CDP
932
     * @param _cdpId The ID of the CDP
933
     * @param _newColl New collateral value, in stETH shares
934
     */
935
    function _setCdpCollShares(bytes32 _cdpId, uint256 _newColl) internal {
936
        Cdps[_cdpId].coll = _newColl;
937
    }
938

                            
                        
939
    /**
940
     * @notice Set the debt of a CDP
941
     * @param _cdpId The ID of the CDP
942
     * @param _newDebt New debt units value
943
     */
944
    function _setCdpDebt(bytes32 _cdpId, uint256 _newDebt) internal {
945
        Cdps[_cdpId].debt = _newDebt;
946
    }
947
}
948

                            
                        

Lines covered: 297 / 322 (92.2%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ICdpManager.sol";
6
import "./Interfaces/ICollSurplusPool.sol";
7
import "./Interfaces/IEBTCToken.sol";
8
import "./Interfaces/ISortedCdps.sol";
9
import "./Dependencies/LiquityBase.sol";
10
import "./Dependencies/ReentrancyGuard.sol";
11
import "./Dependencies/ICollateralTokenOracle.sol";
12
import "./Dependencies/AuthNoOwner.sol";
13

                            
                        
14
/**
15
    @notice CDP Manager storage and shared functions
16
    @dev CDP Manager was split to get around contract size limitations, liquidation related functions are delegated to LiquidationLibrary contract code.
17
    @dev Both must maintain the same storage layout, so shared storage components where placed here
18
    @dev Shared functions were also added here to de-dup code
19
 */
20
contract CdpManagerStorage is LiquityBase, ReentrancyGuard, ICdpManagerData, AuthNoOwner {
21
    // TODO: IMPROVE
22
    // NOTE: No packing cause it's the last var, no need for u64
23
    uint128 public constant UNSET_TIMESTAMP = type(uint128).max;
24
    uint128 public constant MINIMUM_GRACE_PERIOD = 15 minutes;
25

                            
                        
26
    // TODO: IMPROVE THIS!!!
27
    uint128 public lastGracePeriodStartTimestamp = UNSET_TIMESTAMP; // use max to signify
28
    uint128 public recoveryModeGracePeriod = MINIMUM_GRACE_PERIOD;
29

                            
                        
30
    // TODO: Pitfal is fee split // NOTE: Solved by calling `syncGracePeriod` on external operations from BO
31

                            
                        
32
    /// @notice Start the recovery mode grace period, if the system is in RM and the grace period timestamp has not already been set
33
    /// @dev Trusted function to allow BorrowerOperations actions to set RM Grace Period
34
    /// @dev Assumes BorrowerOperations has correctly calculated and passed in the new system TCR
35
    /// @dev To maintain CEI compliance we use this trusted function
36
    function notifyStartGracePeriod(uint256 tcr) external {
37
        _requireCallerIsBorrowerOperations();
38
        _startGracePeriod(tcr);
39
    }
40

                            
                        
41
    /// @notice End the recovery mode grace period, if the system is no longer in RM
42
    /// @dev Trusted function to allow BorrowerOperations actions to set RM Grace Period
43
    /// @dev Assumes BorrowerOperations has correctly calculated and passed in the new system TCR
44
    /// @dev To maintain CEI compliance we use this trusted function
45
    function notifyEndGracePeriod(uint256 tcr) external {
46
        _requireCallerIsBorrowerOperations();
47
        _endGracePeriod(tcr);
48
    }
49

                            
                        
50
    /// @dev Internal notify called by Redemptions and Liquidations
51
    /// @dev Specified TCR is emitted for notification pruposes regardless of whether the Grace Period timestamp is set
52
    function _startGracePeriod(uint256 _tcr) internal {
53
        emit TCRNotified(_tcr);
54

                            
                        
55
        if (lastGracePeriodStartTimestamp == UNSET_TIMESTAMP) {
56
            lastGracePeriodStartTimestamp = uint128(block.timestamp);
57

                            
                        
58
            emit GracePeriodStart();
59
        }
60
    }
61

                            
                        
62
    /// @notice Clear RM Grace Period timestamp if it has been set
63
    /// @notice No input validation, calling function must confirm that the system is not in recovery mode to be valid
64
    /// @dev Specified TCR is emitted for notification pruposes regardless of whether the Grace Period timestamp is set
65
    /// @dev Internal notify called by Redemptions and Liquidations
66
    function _endGracePeriod(uint256 _tcr) internal {
67
        emit TCRNotified(_tcr);
68

                            
                        
69
        if (lastGracePeriodStartTimestamp != UNSET_TIMESTAMP) {
70
            lastGracePeriodStartTimestamp = UNSET_TIMESTAMP;
71

                            
                        
72
            emit GracePeriodEnd();
73
        }
74
    }
75

                            
                        
76
    function _syncGracePeriod() internal {
77
        uint256 price = priceFeed.fetchPrice();
78
        uint256 tcr = _getTCR(price);
79
        bool isRecoveryMode = _checkRecoveryModeForTCR(tcr);
80

                            
                        
81
        if (isRecoveryMode) {
82
            _startGracePeriod(tcr);
83
        } else {
84
            _endGracePeriod(tcr);
85
        }
86
    }
87

                            
                        
88
    /// @dev Set RM grace period based on specified system collShares, system debt, and price
89
    /// @dev Variant for internal use in redemptions and liquidations
90
    function _syncGracePeriodForGivenValues(
91
        uint256 systemCollShares,
92
        uint256 systemDebt,
93
        uint256 price
94
    ) internal {
95
        // Compute TCR with specified values
96
        uint256 newTCR = LiquityMath._computeCR(
97
            collateral.getPooledEthByShares(systemCollShares),
98
            systemDebt,
99
            price
100
        );
101

                            
                        
102
        if (newTCR < CCR) {
103
            // Notify system is in RM
104
            _startGracePeriod(newTCR);
105
        } else {
106
            // Notify system is outside RM
107
            _endGracePeriod(newTCR);
108
        }
109
    }
110

                            
                        
111
    /// @notice Set grace period duratin
112
    /// @notice Permissioned governance function, must set grace period duration above hardcoded minimum
113
    /// @param _gracePeriod new grace period duration, in seconds
114
    function setGracePeriod(uint128 _gracePeriod) external requiresAuth {
115
        require(
116
            _gracePeriod >= MINIMUM_GRACE_PERIOD,
117
            "CdpManager: Grace period below minimum duration"
118
        );
119

                            
                        
120
        syncGlobalAccountingAndGracePeriod();
121
        recoveryModeGracePeriod = _gracePeriod;
122
        emit GracePeriodSet(_gracePeriod);
123
    }
124

                            
                        
125
    string public constant NAME = "CdpManager";
126

                            
                        
127
    // --- Connected contract declarations ---
128

                            
                        
129
    address public immutable borrowerOperationsAddress;
130

                            
                        
131
    ICollSurplusPool immutable collSurplusPool;
132

                            
                        
133
    IEBTCToken public immutable override ebtcToken;
134

                            
                        
135
    address public immutable liquidationLibrary;
136

                            
                        
137
    // A doubly linked list of Cdps, sorted by their sorted by their collateral ratios
138
    ISortedCdps public immutable sortedCdps;
139

                            
                        
140
    // --- Data structures ---
141

                            
                        
142
    uint256 public constant SECONDS_IN_ONE_MINUTE = 60;
143

                            
                        
144
    uint256 public constant MIN_REDEMPTION_FEE_FLOOR = (DECIMAL_PRECISION * 5) / 1000; // 0.5%
145
    uint256 public redemptionFeeFloor = MIN_REDEMPTION_FEE_FLOOR;
146
    bool public redemptionsPaused;
147
    /*
148
     * Half-life of 12h. 12h = 720 min
149
     * (1/2) = d^720 => d = (1/2)^(1/720)
150
     */
151
    uint256 public minuteDecayFactor = 999037758833783000;
152
    uint256 public constant MIN_MINUTE_DECAY_FACTOR = 1; // Non-zero
153
    uint256 public constant MAX_MINUTE_DECAY_FACTOR = 999999999999999999; // Corresponds to a very fast decay rate, but not too extreme
154

                            
                        
155
    uint256 internal immutable deploymentStartTime;
156

                            
                        
157
    /*
158
     * BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction,
159
     * in order to calc the new base rate from a redemption.
160
     * Corresponds to (1 / ALPHA) in the white paper.
161
     */
162
    uint256 public beta = 2;
163

                            
                        
164
    uint256 public baseRate;
165

                            
                        
166
    uint256 public stakingRewardSplit;
167

                            
                        
168
    // The timestamp of the latest fee operation (redemption or new EBTC issuance)
169
    uint256 public lastRedemptionTimestamp;
170

                            
                        
171
    mapping(bytes32 => Cdp) public Cdps;
172

                            
                        
173
    uint256 public override totalStakes;
174

                            
                        
175
    // Snapshot of the value of totalStakes, taken immediately after the latest liquidation and split fee claim
176
    uint256 public totalStakesSnapshot;
177

                            
                        
178
    // Snapshot of the total collateral across the ActivePool, immediately after the latest liquidation and split fee claim
179
    uint256 public totalCollateralSnapshot;
180

                            
                        
181
    /*
182
     * systemDebtRedistributionIndex track the sums of accumulated liquidation rewards per unit staked.
183
     * During its lifetime, each stake earns:
184
     *
185
     * A systemDebt increase  of ( stake * [systemDebtRedistributionIndex - systemDebtRedistributionIndex(0)] )
186
     *
187
     * Where systemDebtRedistributionIndex(0) are snapshots of systemDebtRedistributionIndex
188
     * for the active Cdp taken at the instant the stake was made
189
     */
190
    uint256 public systemDebtRedistributionIndex;
191

                            
                        
192
    /* Global Index for (Full Price Per Share) of underlying collateral token */
193
    uint256 public override stEthIndex;
194
    /* Global Fee accumulator (never decreasing) per stake unit in CDPManager, similar to systemDebtRedistributionIndex */
195
    uint256 public override systemStEthFeePerUnitIndex;
196
    /* Global Fee accumulator calculation error due to integer division, similar to redistribution calculation */
197
    uint256 public override systemStEthFeePerUnitIndexError;
198
    /* Individual CDP Fee accumulator tracker, used to calculate fee split distribution */
199
    mapping(bytes32 => uint256) public stEthFeePerUnitIndex;
200
    /* Update timestamp for global index */
201
    uint256 lastIndexTimestamp;
202
    // Map active cdps to their RewardSnapshot (eBTC debt redistributed)
203
    mapping(bytes32 => uint256) public debtRedistributionIndex;
204

                            
                        
205
    // Array of all active cdp Ids - used to to compute an approximate hint off-chain, for the sorted list insertion
206
    bytes32[] public CdpIds;
207

                            
                        
208
    // Error trackers for the cdp redistribution calculation
209
    uint256 public lastETHError_Redistribution;
210
    uint256 public lastEBTCDebtErrorRedistribution;
211

                            
                        
212
    constructor(
213
        address _liquidationLibraryAddress,
214
        address _authorityAddress,
215
        address _borrowerOperationsAddress,
216
        address _collSurplusPool,
217
        address _ebtcToken,
218
        address _sortedCdps,
219
        address _activePool,
220
        address _priceFeed,
221
        address _collateral
222
    ) LiquityBase(_activePool, _priceFeed, _collateral) {
223
        // TODO: Move to setAddresses or _tickInterest?
224
        deploymentStartTime = block.timestamp;
225
        liquidationLibrary = _liquidationLibraryAddress;
226

                            
                        
227
        _initializeAuthority(_authorityAddress);
228

                            
                        
229
        borrowerOperationsAddress = _borrowerOperationsAddress;
230
        collSurplusPool = ICollSurplusPool(_collSurplusPool);
231
        ebtcToken = IEBTCToken(_ebtcToken);
232
        sortedCdps = ISortedCdps(_sortedCdps);
233
    }
234

                            
                        
235
    /**
236
        @notice BorrowerOperations and CdpManager share reentrancy status by confirming the other's locked flag before beginning operation
237
        @dev This is an alternative to the more heavyweight solution of both being able to set the reentrancy flag on a 3rd contract.
238
     */
239
    modifier nonReentrantSelfAndBOps() {
240
        require(locked == OPEN, "CdpManager: Reentrancy in nonReentrant call");
241
        require(
242
            ReentrancyGuard(borrowerOperationsAddress).locked() == OPEN,
243
            "BorrowerOperations: Reentrancy in nonReentrant call"
244
        );
245

                            
                        
246
        locked = LOCKED;
247

                            
                        
248
        _;
249

                            
                        
250
        locked = OPEN;
251
    }
252

                            
                        
253
    function _closeCdp(bytes32 _cdpId, Status closedStatus) internal {
254
        _closeCdpWithoutRemovingSortedCdps(_cdpId, closedStatus);
255
        sortedCdps.remove(_cdpId);
256
    }
257

                            
                        
258
    function _closeCdpWithoutRemovingSortedCdps(bytes32 _cdpId, Status closedStatus) internal {
259
        require(
260
            closedStatus != Status.nonExistent && closedStatus != Status.active,
261
            "CdpManagerStorage: close non-exist or non-active CDP!"
262
        );
263

                            
                        
264
        uint256 CdpIdsArrayLength = CdpIds.length;
265
        _requireMoreThanOneCdpInSystem(CdpIdsArrayLength);
266

                            
                        
267
        Cdps[_cdpId].status = closedStatus;
268
        Cdps[_cdpId].coll = 0;
269
        Cdps[_cdpId].debt = 0;
270
        Cdps[_cdpId].liquidatorRewardShares = 0;
271

                            
                        
272
        debtRedistributionIndex[_cdpId] = 0;
273
        stEthFeePerUnitIndex[_cdpId] = 0;
274

                            
                        
275
        _removeCdp(_cdpId, CdpIdsArrayLength);
276
    }
277

                            
                        
278
    /*
279
     * Updates snapshots of system total stakes and total collateral,
280
     * excluding a given collateral remainder from the calculation.
281
     * Used in a liquidation sequence.
282
     *
283
     * The calculation excludes a portion of collateral that is in the ActivePool:
284
     *
285
     * the total ETH gas compensation from the liquidation sequence
286
     *
287
     * The ETH as compensation must be excluded as it is always sent out at the very end of the liquidation sequence.
288
     */
289
    function _updateSystemSnapshotsExcludeCollRemainder(uint256 _collRemainder) internal {
290
        uint256 _totalStakesSnapshot = totalStakes;
291
        totalStakesSnapshot = _totalStakesSnapshot;
292

                            
                        
293
        uint256 _totalCollateralSnapshot = activePool.getSystemCollShares() - _collRemainder;
294
        totalCollateralSnapshot = _totalCollateralSnapshot;
295

                            
                        
296
        emit SystemSnapshotsUpdated(_totalStakesSnapshot, _totalCollateralSnapshot);
297
    }
298

                            
                        
299
    /**
300
    get the pending Cdp debt "reward" (i.e. the amount of extra debt assigned to the Cdp) from liquidation redistribution events, earned by their stake
301
    */
302
    function _getPendingRedistributedDebt(
303
        bytes32 _cdpId
304
    ) internal view returns (uint256 pendingEBTCDebtReward) {
305
        Cdp storage cdp = Cdps[_cdpId];
306

                            
                        
307
        if (cdp.status != Status.active) {
308
            return 0;
309
        }
310

                            
                        
311
        uint256 rewardPerUnitStaked = systemDebtRedistributionIndex -
312
            debtRedistributionIndex[_cdpId];
313

                            
                        
314
        if (rewardPerUnitStaked > 0) {
315
            pendingEBTCDebtReward = (cdp.stake * rewardPerUnitStaked) / DECIMAL_PRECISION;
316
        }
317
    }
318

                            
                        
319
    function _hasRedistributedDebt(bytes32 _cdpId) internal view returns (bool) {
320
        /*
321
         * A Cdp has pending rewards if its snapshot is less than the current rewards per-unit-staked sum:
322
         * this indicates that rewards have occured since the snapshot was made, and the user therefore has
323
         * pending rewards
324
         */
325
        if (Cdps[_cdpId].status != Status.active) {
326
            return false;
327
        }
328

                            
                        
329
        // Returns true if there have been any redemptions
330
        return (debtRedistributionIndex[_cdpId] < systemDebtRedistributionIndex);
331
    }
332

                            
                        
333
    function _updateRedistributedDebtSnapshot(bytes32 _cdpId) internal {
334
        uint256 _L_EBTCDebt = systemDebtRedistributionIndex;
335

                            
                        
336
        debtRedistributionIndex[_cdpId] = _L_EBTCDebt;
337
        emit CdpDebtRedistributionIndexUpdated(_cdpId, _L_EBTCDebt);
338
    }
339

                            
                        
340
    // Add the borrowers's coll and debt rewards earned from redistributions, to their Cdp
341
    function _syncAccounting(bytes32 _cdpId) internal {
342
        (, uint _newDebt, , uint _pendingDebt) = _applyAccumulatedFeeSplit(_cdpId);
343

                            
                        
344
        // Update pending debts
345
        if (_pendingDebt > 0) {
346
            Cdp storage _cdp = Cdps[_cdpId];
347
            uint256 prevDebt = _cdp.debt;
348
            uint256 prevColl = _cdp.coll;
349

                            
                        
350
            // Apply pending rewards to cdp's state
351
            _cdp.debt = _newDebt;
352

                            
                        
353
            _updateRedistributedDebtSnapshot(_cdpId);
354

                            
                        
355
            emit CdpUpdated(
356
                _cdpId,
357
                ISortedCdps(sortedCdps).getOwnerAddress(_cdpId),
358
                prevDebt,
359
                prevColl,
360
                _newDebt,
361
                prevColl,
362
                Cdps[_cdpId].stake,
363
                CdpOperation.syncAccounting
364
            );
365
        }
366
    }
367

                            
                        
368
    // Remove borrower's stake from the totalStakes sum, and set their stake to 0
369
    function _removeStake(bytes32 _cdpId) internal {
370
        uint256 _newTotalStakes = totalStakes - Cdps[_cdpId].stake;
371
        totalStakes = _newTotalStakes;
372
        Cdps[_cdpId].stake = 0;
373
        emit TotalStakesUpdated(_newTotalStakes);
374
    }
375

                            
                        
376
    // Update borrower's stake based on their latest collateral value
377
    // and update totalStakes accordingly as well
378
    function _updateStakeAndTotalStakes(bytes32 _cdpId) internal returns (uint256) {
379
        (uint256 newStake, uint256 oldStake) = _updateStakeForCdp(_cdpId);
380

                            
                        
381
        uint256 _newTotalStakes = totalStakes + newStake - oldStake;
382
        totalStakes = _newTotalStakes;
383

                            
                        
384
        emit TotalStakesUpdated(_newTotalStakes);
385

                            
                        
386
        return newStake;
387
    }
388

                            
                        
389
    // Update borrower's stake based on their latest collateral value
390
    function _updateStakeForCdp(bytes32 _cdpId) internal returns (uint256, uint256) {
391
        Cdp storage _cdp = Cdps[_cdpId];
392
        uint256 newStake = _computeNewStake(_cdp.coll);
393
        uint256 oldStake = _cdp.stake;
394
        _cdp.stake = newStake;
395

                            
                        
396
        return (newStake, oldStake);
397
    }
398

                            
                        
399
    // Calculate a new stake based on the snapshots of the totalStakes and totalCollateral taken at the last liquidation
400
    function _computeNewStake(uint256 _coll) internal view returns (uint256) {
401
        uint256 stake;
402
        if (totalCollateralSnapshot == 0) {
403
            stake = _coll;
404
        } else {
405
            /*
406
             * The following check holds true because:
407
             * - The system always contains >= 1 cdp
408
             * - When we close or liquidate a cdp, we redistribute the pending rewards,
409
             * so if all cdps were closed/liquidated,
410
             * rewards would’ve been emptied and totalCollateralSnapshot would be zero too.
411
             */
412
            require(totalStakesSnapshot > 0, "CdpManagerStorage: zero totalStakesSnapshot!");
413
            stake = (_coll * totalStakesSnapshot) / totalCollateralSnapshot;
414
        }
415
        return stake;
416
    }
417

                            
                        
418
    /*
419
     * Remove a Cdp owner from the CdpOwners array, not preserving array order. Removing owner 'B' does the following:
420
     * [A B C D E] => [A E C D], and updates E's Cdp struct to point to its new array index.
421
     */
422
    function _removeCdp(bytes32 _cdpId, uint256 CdpIdsArrayLength) internal {
423
        Status cdpStatus = Cdps[_cdpId].status;
424
        // It’s set in caller function `_closeCdp`
425
        require(
426
            cdpStatus != Status.nonExistent && cdpStatus != Status.active,
427
            "CdpManagerStorage: remove non-exist or non-active CDP!"
428
        );
429

                            
                        
430
        uint128 index = Cdps[_cdpId].arrayIndex;
431
        uint256 length = CdpIdsArrayLength;
432
        uint256 idxLast = length - 1;
433

                            
                        
434
        require(index <= idxLast, "CdpManagerStorage: CDP indexing overflow!");
435

                            
                        
436
        bytes32 idToMove = CdpIds[idxLast];
437

                            
                        
438
        CdpIds[index] = idToMove;
439
        Cdps[idToMove].arrayIndex = index;
440
        emit CdpArrayIndexUpdated(idToMove, index);
441

                            
                        
442
        CdpIds.pop();
443
    }
444

                            
                        
445
    // --- Recovery Mode and TCR functions ---
446

                            
                        
447
    // Calculate TCR given an price, and the entire system coll and debt.
448
    function _computeTCRWithGivenSystemValues(
449
        uint256 _systemCollShares,
450
        uint256 _systemDebt,
451
        uint256 _price
452
    ) internal view returns (uint256) {
453
        uint256 _totalColl = collateral.getPooledEthByShares(_systemCollShares);
454
        return LiquityMath._computeCR(_totalColl, _systemDebt, _price);
455
    }
456

                            
                        
457
    // --- Staking-Reward Fee split functions ---
458

                            
                        
459
    // Claim split fee if there is staking-reward coming
460
    // and update global index & fee-per-unit variables
461
    /// @dev BO can call this without trigggering a
462
    function syncGlobalAccounting() external {
463
        _requireCallerIsBorrowerOperations();
464
        _syncGlobalAccounting();
465
    }
466

                            
                        
467
    function _syncGlobalAccounting() internal {
468
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
469
        _syncStEthIndex(_oldIndex, _newIndex);
470
        if (_newIndex > _oldIndex && totalStakes > 0) {
471
            (
472
                uint256 _feeTaken,
473
                uint256 _newFeePerUnit,
474
                uint256 _perUnitError
475
            ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
476
            _takeSplitAndUpdateFeePerUnit(_feeTaken, _newFeePerUnit, _perUnitError);
477
            _updateSystemSnapshotsExcludeCollRemainder(0);
478
        }
479
    }
480

                            
                        
481
    /// @notice Claim Fee Split, toggles Grace Period accordingly
482
    /// @notice Call this if you want to accrue feeSplit
483
    function syncGlobalAccountingAndGracePeriod() public {
484
        _syncGlobalAccounting(); // Apply // Could trigger RM
485
        _syncGracePeriod(); // Synch Grace Period
486
    }
487

                            
                        
488
    /// @return existing(old) local stETH index AND
489
    /// @return current(new) stETH index from collateral token
490
    function _readStEthIndex() internal view returns (uint256, uint256) {
491
        return (stEthIndex, collateral.getPooledEthByShares(DECIMAL_PRECISION));
492
    }
493

                            
                        
494
    // Update the global index via collateral token
495
    function _syncStEthIndex(uint256 _oldIndex, uint256 _newIndex) internal {
496
        if (_newIndex != _oldIndex) {
497
            stEthIndex = _newIndex;
498
            lastIndexTimestamp = block.timestamp;
499
            emit StEthIndexUpdated(_oldIndex, _newIndex, block.timestamp);
500
        }
501
    }
502

                            
                        
503
    // Calculate fee for given pair of collateral indexes, following are returned values:
504
    // - fee split in collateral token which will be deduced from current total system collateral
505
    // - fee split increase per unit, used to update systemStEthFeePerUnitIndex
506
    // - fee split calculation error, used to update systemStEthFeePerUnitIndexError
507
    function calcFeeUponStakingReward(
508
        uint256 _newIndex,
509
        uint256 _prevIndex
510
    ) public view returns (uint256, uint256, uint256) {
511
        require(_newIndex > _prevIndex, "CDPManager: only take fee with bigger new index");
512
        uint256 deltaIndex = _newIndex - _prevIndex;
513
        uint256 deltaIndexFees = (deltaIndex * stakingRewardSplit) / MAX_REWARD_SPLIT;
514

                            
                        
515
        // we take the fee for all CDPs immediately which is scaled by index precision
516
        uint256 _deltaFeeSplit = deltaIndexFees * getSystemCollShares();
517
        uint256 _cachedAllStakes = totalStakes;
518
        // return the values to update the global fee accumulator
519
        uint256 _feeTaken = collateral.getSharesByPooledEth(_deltaFeeSplit) / DECIMAL_PRECISION;
520
        uint256 _deltaFeeSplitShare = (_feeTaken * DECIMAL_PRECISION) +
521
            systemStEthFeePerUnitIndexError;
522
        uint256 _deltaFeePerUnit = _deltaFeeSplitShare / _cachedAllStakes;
523
        uint256 _perUnitError = _deltaFeeSplitShare - (_deltaFeePerUnit * _cachedAllStakes);
524
        return (_feeTaken, _deltaFeePerUnit, _perUnitError);
525
    }
526

                            
                        
527
    // Take the cut from staking reward
528
    // and update global fee-per-unit accumulator
529
    function _takeSplitAndUpdateFeePerUnit(
530
        uint256 _feeTaken,
531
        uint256 _newPerUnit,
532
        uint256 _newErrorPerUnit
533
    ) internal {
534
        uint256 _oldPerUnit = systemStEthFeePerUnitIndex;
535

                            
                        
536
        systemStEthFeePerUnitIndex = _newPerUnit;
537
        systemStEthFeePerUnitIndexError = _newErrorPerUnit;
538

                            
                        
539
        require(activePool.getSystemCollShares() > _feeTaken, "CDPManager: fee split is too big");
540
        activePool.allocateSystemCollSharesToFeeRecipient(_feeTaken);
541

                            
                        
542
        emit CollateralFeePerUnitUpdated(_oldPerUnit, _newPerUnit, _feeTaken);
543
    }
544

                            
                        
545
    // Apply accumulated fee split distributed to the CDP
546
    // and update its accumulator tracker accordingly
547
    function _applyAccumulatedFeeSplit(
548
        bytes32 _cdpId
549
    ) internal returns (uint256, uint256, uint256, uint256) {
550
        // TODO Ensure global states like systemStEthFeePerUnitIndex get timely updated
551
        // whenever there is a CDP modification operation,
552
        // such as opening, closing, adding collateral, repaying debt, or liquidating
553
        // OR Should we utilize some bot-keeper to work the routine job at fixed interval?
554
        _syncGlobalAccounting();
555

                            
                        
556
        uint256 _oldPerUnitCdp = stEthFeePerUnitIndex[_cdpId];
557
        uint256 _systemStEthFeePerUnitIndex = systemStEthFeePerUnitIndex;
558

                            
                        
559
        (
560
            uint256 _newColl,
561
            uint256 _newDebt,
562
            uint256 _feeSplitDistributed,
563
            uint _pendingDebt
564
        ) = _calcSyncedAccounting(_cdpId, _oldPerUnitCdp, _systemStEthFeePerUnitIndex);
565

                            
                        
566
        // apply split fee to given CDP
567
        if (_feeSplitDistributed > 0) {
568
            Cdps[_cdpId].coll = _newColl;
569

                            
                        
570
            emit CdpFeeSplitApplied(
571
                _cdpId,
572
                _oldPerUnitCdp,
573
                _systemStEthFeePerUnitIndex,
574
                _feeSplitDistributed,
575
                _newColl
576
            );
577
        }
578

                            
                        
579
        // sync per stake index for given CDP
580
        if (_oldPerUnitCdp != _systemStEthFeePerUnitIndex) {
581
            stEthFeePerUnitIndex[_cdpId] = _systemStEthFeePerUnitIndex;
582
        }
583

                            
                        
584
        return (_newColl, _newDebt, _feeSplitDistributed, _pendingDebt);
585
    }
586

                            
                        
587
    // return the applied split fee(scaled by 1e18) and the resulting CDP collateral amount after applied
588
    function getAccumulatedFeeSplitApplied(
589
        bytes32 _cdpId,
590
        uint256 _systemStEthFeePerUnitIndex
591
    ) public view returns (uint256, uint256) {
592
        uint256 _stEthFeePerUnitIndex = stEthFeePerUnitIndex[_cdpId];
593
        uint256 _cdpCol = Cdps[_cdpId].coll;
594

                            
                        
595
        if (
596
            _stEthFeePerUnitIndex == 0 ||
597
            _cdpCol == 0 ||
598
            _stEthFeePerUnitIndex == _systemStEthFeePerUnitIndex
599
        ) {
600
            return (0, _cdpCol);
601
        }
602

                            
                        
603
        uint256 _feeSplitDistributed = Cdps[_cdpId].stake *
604
            (_systemStEthFeePerUnitIndex - _stEthFeePerUnitIndex);
605

                            
                        
606
        uint256 _scaledCdpColl = _cdpCol * DECIMAL_PRECISION;
607

                            
                        
608
        if (_scaledCdpColl > _feeSplitDistributed) {
609
            return (
610
                _feeSplitDistributed,
611
                (_scaledCdpColl - _feeSplitDistributed) / DECIMAL_PRECISION
612
            );
613
        } else {
614
            // extreme unlikely case to skip fee split on this CDP to avoid revert
615
            return (0, _cdpCol);
616
        }
617
    }
618

                            
                        
619
    // -- Modifier functions --
620
    function _requireCdpIsActive(bytes32 _cdpId) internal view {
621
        require(Cdps[_cdpId].status == Status.active, "CdpManager: Cdp does not exist or is closed");
622
    }
623

                            
                        
624
    function _requireMoreThanOneCdpInSystem(uint256 CdpOwnersArrayLength) internal view {
625
        require(
626
            CdpOwnersArrayLength > 1 && sortedCdps.getSize() > 1,
627
            "CdpManager: Only one cdp in the system"
628
        );
629
    }
630

                            
                        
631
    function _requireCallerIsBorrowerOperations() internal view {
632
        require(
633
            msg.sender == borrowerOperationsAddress,
634
            "CdpManager: Caller is not the BorrowerOperations contract"
635
        );
636
    }
637

                            
                        
638
    // --- Helper functions ---
639

                            
                        
640
    // Return the nominal collateral ratio (ICR) of a given Cdp, without the price.
641
    // Takes a cdp's pending coll and debt rewards from redistributions into account.
642
    function getNominalICR(bytes32 _cdpId) external view returns (uint256) {
643
        (uint256 currentEBTCDebt, uint256 currentETH, ) = getDebtAndCollShares(_cdpId);
644

                            
                        
645
        uint256 NICR = LiquityMath._computeNominalCR(currentETH, currentEBTCDebt);
646
        return NICR;
647
    }
648

                            
                        
649
    // Return the nominal collateral ratio (ICR) of a given Cdp, without the price.
650
    // Takes a cdp's pending coll and debt rewards from redistributions into account.
651
    function getSyncedNominalICR(bytes32 _cdpId) external view returns (uint256) {
652
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
653
        (, uint256 _newGlobalSplitIdx, ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
654
        (uint256 _newColl, uint256 _newDebt, , uint256 _pendingDebt) = _calcSyncedAccounting(
655
            _cdpId,
656
            stEthFeePerUnitIndex[_cdpId],
657
            _newGlobalSplitIdx /// NOTE: This is latest index
658
        );
659

                            
                        
660
        uint256 NICR = LiquityMath._computeNominalCR(_newColl, _newDebt);
661
        return NICR;
662
    }
663

                            
                        
664
    // Return the current collateral ratio (ICR) of a given Cdp.
665
    //Takes a cdp's pending coll and debt rewards from redistributions into account.
666
    function getICR(bytes32 _cdpId, uint256 _price) public view returns (uint256) {
667
        (uint256 currentEBTCDebt, uint256 currentETH, ) = getDebtAndCollShares(_cdpId);
668
        uint256 ICR = _calculateCR(currentETH, currentEBTCDebt, _price);
669
        return ICR;
670
    }
671

                            
                        
672
    function _calculateCR(
673
        uint256 currentCollShare,
674
        uint256 currentDebt,
675
        uint256 _price
676
    ) internal view returns (uint256) {
677
        uint256 _underlyingCollateral = collateral.getPooledEthByShares(currentCollShare);
678
        return LiquityMath._computeCR(_underlyingCollateral, currentDebt, _price);
679
    }
680

                            
                        
681
    /**
682
    get the pending Cdp debt "reward" (i.e. the amount of extra debt assigned to the Cdp) from liquidation redistribution events, earned by their stake
683
    */
684
    function getPendingRedistributedDebt(
685
        bytes32 _cdpId
686
    ) public view returns (uint256 pendingEBTCDebtReward) {
687
        return _getPendingRedistributedDebt(_cdpId);
688
    }
689

                            
                        
690
    function hasPendingRedistributedDebt(bytes32 _cdpId) public view returns (bool) {
691
        return _hasRedistributedDebt(_cdpId);
692
    }
693

                            
                        
694
    // Return the Cdps entire debt and coll struct
695
    function _getDebtAndCollShares(
696
        bytes32 _cdpId
697
    ) internal view returns (CdpDebtAndCollShares memory) {
698
        (uint256 entireDebt, uint256 entireColl, uint256 pendingDebtReward) = getDebtAndCollShares(
699
            _cdpId
700
        );
701
        return CdpDebtAndCollShares(entireDebt, entireColl, pendingDebtReward);
702
    }
703

                            
                        
704
    // Return the Cdps entire debt and coll, including pending rewards from redistributions and collateral reduction from split fee.
705
    /// @notice pending rewards are included in the debt and coll totals returned.
706
    function getDebtAndCollShares(
707
        bytes32 _cdpId
708
    ) public view returns (uint256 debt, uint256 coll, uint256 pendingEBTCDebtReward) {
709
        (uint256 _newColl, uint256 _newDebt, , uint256 _pendingDebt) = _calcSyncedAccounting(
710
            _cdpId,
711
            stEthFeePerUnitIndex[_cdpId],
712
            systemStEthFeePerUnitIndex /// @audit This is not necessarily latest
713
        );
714
        coll = _newColl;
715
        debt = _newDebt;
716
        pendingEBTCDebtReward = _pendingDebt;
717
    }
718

                            
                        
719
    /// @dev calculate pending global state change to be applied:
720
    /// @return split fee taken (if any) AND
721
    /// @return new split index per stake unit AND
722
    /// @return new split index error
723
    function _calcSyncedGlobalAccounting(
724
        uint256 _newIndex,
725
        uint256 _oldIndex
726
    ) internal view returns (uint256, uint256, uint256) {
727
        if (_newIndex > _oldIndex && totalStakes > 0) {
728
            /// @audit-ok We don't take the fee if we had a negative rebase
729
            (
730
                uint256 _feeTaken,
731
                uint256 _deltaFeePerUnit,
732
                uint256 _perUnitError
733
            ) = calcFeeUponStakingReward(_newIndex, _oldIndex);
734

                            
                        
735
            // calculate new split per stake unit
736
            uint256 _newPerUnit = systemStEthFeePerUnitIndex + _deltaFeePerUnit;
737
            return (_feeTaken, _newPerUnit, _perUnitError);
738
        } else {
739
            return (0, systemStEthFeePerUnitIndex, systemStEthFeePerUnitIndexError);
740
        }
741
    }
742

                            
                        
743
    /// @dev calculate pending state change to be applied for given CDP and global split index(typically already synced):
744
    /// @return new CDP collateral share after pending change applied
745
    /// @return new CDP debt after pending change applied
746
    /// @return split fee applied to given CDP
747
    /// @return redistributed debt applied to given CDP
748
    function _calcSyncedAccounting(
749
        bytes32 _cdpId,
750
        uint256 _cdpPerUnitIdx,
751
        uint256 _systemStEthFeePerUnitIndex
752
    ) internal view returns (uint256, uint256, uint256, uint256) {
753
        uint256 _feeSplitApplied;
754
        uint256 _newCollShare = Cdps[_cdpId].coll;
755

                            
                        
756
        // processing split fee to be applied
757
        if (_cdpPerUnitIdx != _systemStEthFeePerUnitIndex && _cdpPerUnitIdx > 0) {
758
            (
759
                uint256 _feeSplitDistributed,
760
                uint256 _newCollShareAfter
761
            ) = getAccumulatedFeeSplitApplied(_cdpId, _systemStEthFeePerUnitIndex);
762
            _feeSplitApplied = _feeSplitDistributed;
763
            _newCollShare = _newCollShareAfter;
764
        }
765

                            
                        
766
        // processing redistributed debt to be applied
767
        (uint256 _newDebt, uint256 pendingDebtRedistributed) = _getSyncedCdpDebtAndRedistribution(
768
            _cdpId
769
        );
770

                            
                        
771
        return (_newCollShare, _newDebt, _feeSplitApplied, pendingDebtRedistributed);
772
    }
773

                            
                        
774
    /// @return CDP debt and pending redistribution from liquidation applied
775
    function _getSyncedCdpDebtAndRedistribution(
776
        bytes32 _cdpId
777
    ) internal view returns (uint256, uint256) {
778
        uint256 pendingDebtRedistributed = _getPendingRedistributedDebt(_cdpId);
779
        uint256 _newDebt = Cdps[_cdpId].debt;
780
        if (pendingDebtRedistributed > 0) {
781
            _newDebt = _newDebt + pendingDebtRedistributed;
782
        }
783
        return (_newDebt, pendingDebtRedistributed);
784
    }
785

                            
                        
786
    /// @return CDP debt with pending redistribution from liquidation applied
787
    function getSyncedCdpDebt(bytes32 _cdpId) public view returns (uint256) {
788
        (uint256 _newDebt, ) = _getSyncedCdpDebtAndRedistribution(_cdpId);
789
        return _newDebt;
790
    }
791

                            
                        
792
    /// @return CDP collateral with pending split fee applied
793
    function getSyncedCdpCollShares(bytes32 _cdpId) public view returns (uint256) {
794
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
795
        (, uint256 _newGlobalSplitIdx, ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
796
        (uint256 _newColl, , , ) = _calcSyncedAccounting(
797
            _cdpId,
798
            stEthFeePerUnitIndex[_cdpId],
799
            _newGlobalSplitIdx
800
        );
801
        return _newColl;
802
    }
803

                            
                        
804
    /// @return CDP ICR with pending collateral and debt change applied
805
    function getSyncedICR(bytes32 _cdpId, uint256 _price) public view returns (uint256) {
806
        uint256 _debt = getSyncedCdpDebt(_cdpId);
807
        uint256 _collShare = getSyncedCdpCollShares(_cdpId);
808
        return _calculateCR(_collShare, _debt, _price);
809
    }
810

                            
                        
811
    /// @return TCR with pending collateral and debt change applied
812
    function getSyncedTCR(uint256 _price) public view returns (uint256) {
813
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
814
        (uint256 _feeTaken, , ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
815

                            
                        
816
        uint256 _systemCollShare = activePool.getSystemCollShares();
817
        if (_feeTaken > 0) {
818
            _systemCollShare = _systemCollShare - _feeTaken;
819
        }
820
        uint256 _systemDebt = activePool.getSystemDebt();
821
        return _calculateCR(_systemCollShare, _systemDebt, _price);
822
    }
823

                            
                        
824
    // Can liquidate in RM if ICR < TCR AND Enough time has passed
825
    function canLiquidateRecoveryMode(uint256 icr, uint256 tcr) public view returns (bool) {
826
        // ICR < TCR and we have waited enough
827
        uint128 cachedLastGracePeriodStartTimestamp = lastGracePeriodStartTimestamp;
828
        return
829
            icr < tcr &&
830
            cachedLastGracePeriodStartTimestamp != UNSET_TIMESTAMP &&
831
            block.timestamp > cachedLastGracePeriodStartTimestamp + recoveryModeGracePeriod;
832
    }
833
}
834

                            
                        

Lines covered: 20 / 42 (47.6%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ICollSurplusPool.sol";
6
import "./Dependencies/ICollateralToken.sol";
7
import "./Dependencies/SafeERC20.sol";
8
import "./Dependencies/ReentrancyGuard.sol";
9
import "./Dependencies/AuthNoOwner.sol";
10
import "./Interfaces/IActivePool.sol";
11

                            
                        
12
contract CollSurplusPool is ICollSurplusPool, ReentrancyGuard, AuthNoOwner {
13
    using SafeERC20 for IERC20;
14

                            
                        
15
    string public constant NAME = "CollSurplusPool";
16

                            
                        
17
    address public immutable borrowerOperationsAddress;
18
    address public immutable cdpManagerAddress;
19
    address public immutable activePoolAddress;
20
    address public immutable feeRecipientAddress;
21
    ICollateralToken public immutable collateral;
22

                            
                        
23
    // deposited ether tracker
24
    uint256 internal totalSurplusCollShares;
25
    // Collateral surplus claimable by cdp owners
26
    mapping(address => uint256) internal balances;
27

                            
                        
28
    // --- Contract setters ---
29

                            
                        
30
    /**
31
     * @notice Sets the addresses of the contracts and renounces ownership
32
     * @dev One-time initialization function. Can only be called by the owner as a security measure. Ownership is renounced after the function is called.
33
     * @param _borrowerOperationsAddress The address of the BorrowerOperations
34
     * @param _cdpManagerAddress The address of the CDPManager
35
     * @param _activePoolAddress The address of the ActivePool
36
     * @param _collTokenAddress The address of the CollateralToken
37
     */
38
    constructor(
39
        address _borrowerOperationsAddress,
40
        address _cdpManagerAddress,
41
        address _activePoolAddress,
42
        address _collTokenAddress
43
    ) {
44
        borrowerOperationsAddress = _borrowerOperationsAddress;
45
        cdpManagerAddress = _cdpManagerAddress;
46
        activePoolAddress = _activePoolAddress;
47
        collateral = ICollateralToken(_collTokenAddress);
48
        feeRecipientAddress = IActivePool(activePoolAddress).feeRecipientAddress();
49

                            
                        
50
        address _authorityAddress = address(AuthNoOwner(cdpManagerAddress).authority());
51
        if (_authorityAddress != address(0)) {
52
            _initializeAuthority(_authorityAddress);
53
        }
54
    }
55

                            
                        
56
    /**
57
     * @notice Gets the current collateral state variable of the pool
58
     * @dev Not necessarily equal to the raw collateral token balance - tokens can be forcibly sent to contracts
59
     * @return The current collateral balance tracked by the variable
60
     */
61
    function getTotalSurplusCollShares() external view override returns (uint256) {
62
        return totalSurplusCollShares;
63
    }
64

                            
                        
65
    /**
66
     * @notice Gets the collateral surplus available for the given account
67
     * @param _account The address of the account
68
     * @return The collateral balance available to claim
69
     */
70
    function getSurplusCollShares(address _account) external view override returns (uint256) {
71
        return balances[_account];
72
    }
73

                            
                        
74
    // --- Pool functionality ---
75

                            
                        
76
    function increaseSurplusCollShares(address _account, uint256 _amount) external override {
77
        _requireCallerIsCdpManager();
78

                            
                        
79
        uint256 newAmount = balances[_account] + _amount;
80
        balances[_account] = newAmount;
81

                            
                        
82
        emit SurplusCollSharesUpdated(_account, newAmount);
83
    }
84

                            
                        
85
    function claimSurplusCollShares(address _account) external override {
86
        _requireCallerIsBorrowerOperations();
87
        uint256 claimableColl = balances[_account];
88
        require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");
89

                            
                        
90
        balances[_account] = 0;
91
        emit SurplusCollSharesUpdated(_account, 0);
92

                            
                        
93
        uint256 cachedTotalSurplusCollShares = totalSurplusCollShares;
94

                            
                        
95
        require(cachedTotalSurplusCollShares >= claimableColl, "!CollSurplusPoolBal");
96
        // Safe per the check above
97
        unchecked {
98
            totalSurplusCollShares = cachedTotalSurplusCollShares - claimableColl;
99
        }
100
        emit CollSharesTransferred(_account, claimableColl);
101

                            
                        
102
        // NOTE: No need for safe transfer if the collateral asset is standard. Make sure this is the case!
103
        collateral.transferShares(_account, claimableColl);
104
    }
105

                            
                        
106
    // --- 'require' functions ---
107

                            
                        
108
    function _requireCallerIsBorrowerOperations() internal view {
109
        require(
110
            msg.sender == borrowerOperationsAddress,
111
            "CollSurplusPool: Caller is not Borrower Operations"
112
        );
113
    }
114

                            
                        
115
    function _requireCallerIsCdpManager() internal view {
116
        require(msg.sender == cdpManagerAddress, "CollSurplusPool: Caller is not CdpManager");
117
    }
118

                            
                        
119
    function _requireCallerIsActivePool() internal view {
120
        require(msg.sender == activePoolAddress, "CollSurplusPool: Caller is not Active Pool");
121
    }
122

                            
                        
123
    function increaseTotalSurplusCollShares(uint256 _value) external override {
124
        _requireCallerIsActivePool();
125
        totalSurplusCollShares = totalSurplusCollShares + _value;
126
    }
127

                            
                        
128
    // === Governed Functions === //
129

                            
                        
130
    /// @dev Function to move unintended dust that are not protected
131
    /// @notice moves given amount of given token (collateral is NOT allowed)
132
    /// @notice because recipient are fixed, this function is safe to be called by anyone
133
    function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {
134
        require(token != address(collateral), "CollSurplusPool: Cannot Sweep Collateral");
135

                            
                        
136
        uint256 balance = IERC20(token).balanceOf(address(this));
137
        require(amount <= balance, "CollSurplusPool: Attempt to sweep more than balance");
138

                            
                        
139
        IERC20(token).safeTransfer(feeRecipientAddress, amount);
140

                            
                        
141
        emit SweepTokenSuccess(token, amount, feeRecipientAddress);
142
    }
143
}
144

                            
                        

Lines covered: 0 / 19 (0.0%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
/**
7
 * @dev Collection of functions related to the address type
8
 */
9
library Address {
10
    /**
11
     * @dev Returns true if `account` is a contract.
12
     *
13
     * [IMPORTANT]
14
     * ====
15
     * It is unsafe to assume that an address for which this function returns
16
     * false is an externally-owned account (EOA) and not a contract.
17
     *
18
     * Among others, `isContract` will return false for the following
19
     * types of addresses:
20
     *
21
     *  - an externally-owned account
22
     *  - a contract in construction
23
     *  - an address where a contract will be created
24
     *  - an address where a contract lived, but was destroyed
25
     *
26
     * Furthermore, `isContract` will also return true if the target contract within
27
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
28
     * which only has an effect at the end of a transaction.
29
     * ====
30
     *
31
     * [IMPORTANT]
32
     * ====
33
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
34
     *
35
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
36
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
37
     * constructor.
38
     * ====
39
     */
40
    function isContract(address account) internal view returns (bool) {
41
        // This method relies on extcodesize/address.code.length, which returns 0
42
        // for contracts in construction, since the code is only stored at the end
43
        // of the constructor execution.
44

                            
                        
45
        return account.code.length > 0;
46
    }
47

                            
                        
48
    /**
49
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
50
     * `errorMessage` as a fallback revert reason when `target` reverts.
51
     *
52
     * _Available since v3.1._
53
     */
54
    function functionCall(
55
        address target,
56
        bytes memory data,
57
        string memory errorMessage
58
    ) internal returns (bytes memory) {
59
        return functionCallWithValue(target, data, 0, errorMessage);
60
    }
61

                            
                        
62
    /**
63
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
64
     * with `errorMessage` as a fallback revert reason when `target` reverts.
65
     *
66
     * _Available since v3.1._
67
     */
68
    function functionCallWithValue(
69
        address target,
70
        bytes memory data,
71
        uint256 value,
72
        string memory errorMessage
73
    ) internal returns (bytes memory) {
74
        require(address(this).balance >= value, "Address: insufficient balance for call");
75
        (bool success, bytes memory returndata) = target.call{value: value}(data);
76
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
77
    }
78

                            
                        
79
    /**
80
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
81
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
82
     *
83
     * _Available since v4.8._
84
     */
85
    function verifyCallResultFromTarget(
86
        address target,
87
        bool success,
88
        bytes memory returndata,
89
        string memory errorMessage
90
    ) internal view returns (bytes memory) {
91
        if (success) {
92
            if (returndata.length == 0) {
93
                // only check isContract if the call was successful and the return data is empty
94
                // otherwise we already know that it was a contract
95
                require(isContract(target), "Address: call to non-contract");
96
            }
97
            return returndata;
98
        } else {
99
            _revert(returndata, errorMessage);
100
        }
101
    }
102

                            
                        
103
    function _revert(bytes memory returndata, string memory errorMessage) private pure {
104
        // Look for revert reason and bubble it up if present
105
        if (returndata.length > 0) {
106
            // The easiest way to bubble the revert reason is using memory via assembly
107
            /// @solidity memory-safe-assembly
108
            assembly {
109
                let returndata_size := mload(returndata)
110
                revert(add(32, returndata), returndata_size)
111
            }
112
        } else {
113
            revert(errorMessage);
114
        }
115
    }
116
}
117

                            
                        

Lines covered: 9 / 16 (56.2%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import {Authority} from "./Authority.sol";
5

                            
                        
6
/// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
7
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
8
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
9
abstract contract Auth {
10
    event OwnershipTransferred(address indexed user, address indexed newOwner);
11

                            
                        
12
    event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
13

                            
                        
14
    address public owner;
15

                            
                        
16
    Authority public authority;
17

                            
                        
18
    constructor(address _owner, Authority _authority) {
19
        owner = _owner;
20
        authority = _authority;
21

                            
                        
22
        emit OwnershipTransferred(msg.sender, _owner);
23
        emit AuthorityUpdated(msg.sender, _authority);
24
    }
25

                            
                        
26
    modifier requiresAuth() virtual {
27
        require(isAuthorized(msg.sender, msg.sig), "Auth: UNAUTHORIZED");
28

                            
                        
29
        _;
30
    }
31

                            
                        
32
    function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
33
        Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
34

                            
                        
35
        // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
36
        // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
37
        return
38
            (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) ||
39
            user == owner;
40
    }
41

                            
                        
42
    function setAuthority(Authority newAuthority) public virtual {
43
        // We check if the caller is the owner first because we want to ensure they can
44
        // always swap out the authority even if it's reverting or using up a lot of gas.
45
        require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));
46

                            
                        
47
        authority = newAuthority;
48

                            
                        
49
        emit AuthorityUpdated(msg.sender, newAuthority);
50
    }
51

                            
                        
52
    function transferOwnership(address newOwner) public virtual requiresAuth {
53
        owner = newOwner;
54

                            
                        
55
        emit OwnershipTransferred(msg.sender, newOwner);
56
    }
57
}
58

                            
                        

Lines covered: 11 / 19 (57.9%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import {Authority} from "./Authority.sol";
5

                            
                        
6
/// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
7
/// @author Modified by BadgerDAO to remove owner
8
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
9
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
10
contract AuthNoOwner {
11
    event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
12

                            
                        
13
    Authority private _authority;
14
    bool private _authorityInitialized;
15

                            
                        
16
    modifier requiresAuth() virtual {
17
        require(isAuthorized(msg.sender, msg.sig), "Auth: UNAUTHORIZED");
18

                            
                        
19
        _;
20
    }
21

                            
                        
22
    function authority() public view returns (Authority) {
23
        return _authority;
24
    }
25

                            
                        
26
    function authorityInitialized() public view returns (bool) {
27
        return _authorityInitialized;
28
    }
29

                            
                        
30
    function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
31
        Authority auth = _authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
32

                            
                        
33
        // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
34
        // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
35
        return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig));
36
    }
37

                            
                        
38
    function setAuthority(address newAuthority) public virtual {
39
        // We check if the caller is the owner first because we want to ensure they can
40
        // always swap out the authority even if it's reverting or using up a lot of gas.
41
        require(_authority.canCall(msg.sender, address(this), msg.sig));
42

                            
                        
43
        _authority = Authority(newAuthority);
44

                            
                        
45
        // Once authority is set once via any means, ensure it is initialized
46
        if (!_authorityInitialized) {
47
            _authorityInitialized = true;
48
        }
49

                            
                        
50
        emit AuthorityUpdated(msg.sender, Authority(newAuthority));
51
    }
52

                            
                        
53
    /// @notice Changed constructor to initialize to allow flexiblity of constructor vs initializer use
54
    /// @notice sets authorityInitiailzed flag to ensure only one use of
55
    function _initializeAuthority(address newAuthority) internal {
56
        require(address(_authority) == address(0), "Auth: authority is non-zero");
57
        require(!_authorityInitialized, "Auth: authority already initialized");
58

                            
                        
59
        _authority = Authority(newAuthority);
60
        _authorityInitialized = true;
61

                            
                        
62
        emit AuthorityUpdated(address(this), Authority(newAuthority));
63
    }
64
}
65

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
/// @notice A generic interface for a contract which provides authorization data to an Auth instance.
5
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
6
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
7
interface Authority {
8
    function canCall(address user, address target, bytes4 functionSig) external view returns (bool);
9
}
10

                            
                        

Lines covered: 1 / 2 (50.0%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
contract BaseMath {
5
    uint256 public constant DECIMAL_PRECISION = 1e18;
6
}
7

                            
                        

Lines covered: 2 / 2 (100.0%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
/**
7
 * @dev Provides information about the current execution context, including the
8
 * sender of the transaction and its data. While these are generally available
9
 * via msg.sender and msg.data, they should not be accessed in such a direct
10
 * manner, since when dealing with meta-transactions the account sending and
11
 * paying for execution may not be the actual sender (as far as an application
12
 * is concerned).
13
 *
14
 * This contract is only required for intermediate, library-like contracts.
15
 */
16
abstract contract Context {
17
    function _msgSender() internal view virtual returns (address) {
18
        return msg.sender;
19
    }
20

                            
                        
21
    function _msgData() internal view virtual returns (bytes calldata) {
22
        return msg.data;
23
    }
24
}
25

                            
                        

Lines covered: 20 / 21 (95.2%)

1
//SPDX-License-Identifier: Unlicense
2
pragma solidity 0.8.17;
3

                            
                        
4
/**
5
  @title A library for deploying contracts EIP-3171 style.
6
  @author Agustin Aguilar <aa@horizon.io>
7
*/
8
library Create3 {
9
    error ErrorCreatingProxy();
10
    error ErrorCreatingContract();
11
    error TargetAlreadyExists();
12

                            
                        
13
    /**
14
    @notice The bytecode for a contract that proxies the creation of another contract
15
    @dev If this code is deployed using CREATE2 it can be used to decouple `creationCode` from the child contract address
16

                            
                        
17
  0x67363d3d37363d34f03d5260086018f3:
18
      0x00  0x67  0x67XXXXXXXXXXXXXXXX  PUSH8 bytecode  0x363d3d37363d34f0
19
      0x01  0x3d  0x3d                  RETURNDATASIZE  0 0x363d3d37363d34f0
20
      0x02  0x52  0x52                  MSTORE
21
      0x03  0x60  0x6008                PUSH1 08        8
22
      0x04  0x60  0x6018                PUSH1 18        24 8
23
      0x05  0xf3  0xf3                  RETURN
24

                            
                        
25
  0x363d3d37363d34f0:
26
      0x00  0x36  0x36                  CALLDATASIZE    cds
27
      0x01  0x3d  0x3d                  RETURNDATASIZE  0 cds
28
      0x02  0x3d  0x3d                  RETURNDATASIZE  0 0 cds
29
      0x03  0x37  0x37                  CALLDATACOPY
30
      0x04  0x36  0x36                  CALLDATASIZE    cds
31
      0x05  0x3d  0x3d                  RETURNDATASIZE  0 cds
32
      0x06  0x34  0x34                  CALLVALUE       val 0 cds
33
      0x07  0xf0  0xf0                  CREATE          addr
34
  */
35

                            
                        
36
    bytes internal constant PROXY_CHILD_BYTECODE =
37
        hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3";
38

                            
                        
39
    //                        KECCAK256_PROXY_CHILD_BYTECODE = keccak256(PROXY_CHILD_BYTECODE);
40
    bytes32 internal constant KECCAK256_PROXY_CHILD_BYTECODE =
41
        0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
42

                            
                        
43
    /**
44
    @notice Returns the size of the code on a given address
45
    @param _addr Address that may or may not contain code
46
    @return size of the code on the given `_addr`
47
  */
48
    function codeSize(address _addr) internal view returns (uint256 size) {
49
        assembly {
50
            size := extcodesize(_addr)
51
        }
52
    }
53

                            
                        
54
    /**
55
    @notice Creates a new contract with given `_creationCode` and `_salt`
56
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
57
    @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
58
    @return addr of the deployed contract, reverts on error
59
  */
60
    function create3(bytes32 _salt, bytes memory _creationCode) internal returns (address addr) {
61
        return create3(_salt, _creationCode, 0);
62
    }
63

                            
                        
64
    /**
65
    @notice Creates a new contract with given `_creationCode` and `_salt`
66
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
67
    @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
68
    @param _value In WEI of ETH to be forwarded to child contract
69
    @return addr of the deployed contract, reverts on error
70
  */
71
    function create3(
72
        bytes32 _salt,
73
        bytes memory _creationCode,
74
        uint256 _value
75
    ) internal returns (address addr) {
76
        // Creation code
77
        bytes memory creationCode = PROXY_CHILD_BYTECODE;
78

                            
                        
79
        // Get target final address
80
        addr = addressOf(_salt);
81
        if (codeSize(addr) != 0) revert TargetAlreadyExists();
82

                            
                        
83
        // Create CREATE2 proxy
84
        address proxy;
85
        assembly {
86
            proxy := create2(0, add(creationCode, 32), mload(creationCode), _salt)
87
        }
88
        if (proxy == address(0)) revert ErrorCreatingProxy();
89

                            
                        
90
        // Call proxy with final init code
91
        (bool success, ) = proxy.call{value: _value}(_creationCode);
92
        if (!success || codeSize(addr) == 0) revert ErrorCreatingContract();
93
    }
94

                            
                        
95
    /**
96
    @notice Computes the resulting address of a contract deployed using address(this) and the given `_salt`
97
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
98
    @return addr of the deployed contract, reverts on error
99

                            
                        
100
    @dev The address creation formula is: keccak256(rlp([keccak256(0xff ++ address(this) ++ _salt ++ keccak256(childBytecode))[12:], 0x01]))
101
  */
102
    function addressOf(bytes32 _salt) internal view returns (address) {
103
        address proxy = address(
104
            uint160(
105
                uint256(
106
                    keccak256(
107
                        abi.encodePacked(
108
                            hex"ff",
109
                            address(this),
110
                            _salt,
111
                            KECCAK256_PROXY_CHILD_BYTECODE
112
                        )
113
                    )
114
                )
115
            )
116
        );
117

                            
                        
118
        return address(uint160(uint256(keccak256(abi.encodePacked(hex"d6_94", proxy, hex"01")))));
119
    }
120
}
121

                            
                        

Lines covered: 1 / 5 (20.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../Interfaces/IERC3156FlashLender.sol";
6
import "../Interfaces/IWETH.sol";
7

                            
                        
8
abstract contract ERC3156FlashLender is IERC3156FlashLender {
9
    // TODO: Fix / Finalize
10
    uint256 public constant MAX_BPS = 10_000;
11
    uint256 public constant MAX_FEE_BPS = 1_000; // 10%
12
    bytes32 public constant FLASH_SUCCESS_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");
13

                            
                        
14
    // Functions to modify these variables must be included in impelemnting contracts if desired
15
    uint16 public feeBps = 3; // may be subject to future adjustments through protocol governance
16
    bool public flashLoansPaused;
17
}
18

                            
                        

Lines covered: 13 / 54 (24.1%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
3
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
4

                            
                        
5
pragma solidity 0.8.17;
6

                            
                        
7
/**
8
 * @dev Library for managing
9
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
10
 * types.
11
 *
12
 * Sets have the following properties:
13
 *
14
 * - Elements are added, removed, and checked for existence in constant time
15
 * (O(1)).
16
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
17
 *
18
 * ```solidity
19
 * contract Example {
20
 *     // Add the library methods
21
 *     using EnumerableSet for EnumerableSet.AddressSet;
22
 *
23
 *     // Declare a set state variable
24
 *     EnumerableSet.AddressSet private mySet;
25
 * }
26
 * ```
27
 *
28
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
29
 * and `uint256` (`UintSet`) are supported.
30
 *
31
 * [WARNING]
32
 * ====
33
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
34
 * unusable.
35
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
36
 *
37
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
38
 * array of EnumerableSet.
39
 * ====
40
 */
41
library EnumerableSet {
42
    // To implement this library for multiple types with as little code
43
    // repetition as possible, we write it in terms of a generic Set type with
44
    // bytes32 values.
45
    // The Set implementation uses private functions, and user-facing
46
    // implementations (such as AddressSet) are just wrappers around the
47
    // underlying Set.
48
    // This means that we can only create new EnumerableSets for types that fit
49
    // in bytes32.
50

                            
                        
51
    struct Set {
52
        // Storage of set values
53
        bytes32[] _values;
54
        // Position of the value in the `values` array, plus 1 because index 0
55
        // means a value is not in the set.
56
        mapping(bytes32 => uint256) _indexes;
57
    }
58

                            
                        
59
    /**
60
     * @dev Add a value to a set. O(1).
61
     *
62
     * Returns true if the value was added to the set, that is if it was not
63
     * already present.
64
     */
65
    function _add(Set storage set, bytes32 value) private returns (bool) {
66
        if (!_contains(set, value)) {
67
            set._values.push(value);
68
            // The value is stored at length-1, but we add 1 to all indexes
69
            // and use 0 as a sentinel value
70
            set._indexes[value] = set._values.length;
71
            return true;
72
        } else {
73
            return false;
74
        }
75
    }
76

                            
                        
77
    /**
78
     * @dev Removes a value from a set. O(1).
79
     *
80
     * Returns true if the value was removed from the set, that is if it was
81
     * present.
82
     */
83
    function _remove(Set storage set, bytes32 value) private returns (bool) {
84
        // We read and store the value's index to prevent multiple reads from the same storage slot
85
        uint256 valueIndex = set._indexes[value];
86

                            
                        
87
        if (valueIndex != 0) {
88
            // Equivalent to contains(set, value)
89
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
90
            // the array, and then remove the last element (sometimes called as 'swap and pop').
91
            // This modifies the order of the array, as noted in {at}.
92

                            
                        
93
            uint256 toDeleteIndex = valueIndex - 1;
94
            uint256 lastIndex = set._values.length - 1;
95

                            
                        
96
            if (lastIndex != toDeleteIndex) {
97
                bytes32 lastValue = set._values[lastIndex];
98

                            
                        
99
                // Move the last value to the index where the value to delete is
100
                set._values[toDeleteIndex] = lastValue;
101
                // Update the index for the moved value
102
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
103
            }
104

                            
                        
105
            // Delete the slot where the moved value was stored
106
            set._values.pop();
107

                            
                        
108
            // Delete the index for the deleted slot
109
            delete set._indexes[value];
110

                            
                        
111
            return true;
112
        } else {
113
            return false;
114
        }
115
    }
116

                            
                        
117
    /**
118
     * @dev Returns true if the value is in the set. O(1).
119
     */
120
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
121
        return set._indexes[value] != 0;
122
    }
123

                            
                        
124
    /**
125
     * @dev Returns the number of values on the set. O(1).
126
     */
127
    function _length(Set storage set) private view returns (uint256) {
128
        return set._values.length;
129
    }
130

                            
                        
131
    /**
132
     * @dev Returns the value stored at position `index` in the set. O(1).
133
     *
134
     * Note that there are no guarantees on the ordering of values inside the
135
     * array, and it may change when more values are added or removed.
136
     *
137
     * Requirements:
138
     *
139
     * - `index` must be strictly less than {length}.
140
     */
141
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
142
        return set._values[index];
143
    }
144

                            
                        
145
    /**
146
     * @dev Return the entire set in an array
147
     *
148
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
149
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
150
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
151
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
152
     */
153
    function _values(Set storage set) private view returns (bytes32[] memory) {
154
        return set._values;
155
    }
156

                            
                        
157
    // Bytes32Set
158

                            
                        
159
    struct Bytes32Set {
160
        Set _inner;
161
    }
162

                            
                        
163
    /**
164
     * @dev Add a value to a set. O(1).
165
     *
166
     * Returns true if the value was added to the set, that is if it was not
167
     * already present.
168
     */
169
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
170
        return _add(set._inner, value);
171
    }
172

                            
                        
173
    /**
174
     * @dev Removes a value from a set. O(1).
175
     *
176
     * Returns true if the value was removed from the set, that is if it was
177
     * present.
178
     */
179
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
180
        return _remove(set._inner, value);
181
    }
182

                            
                        
183
    /**
184
     * @dev Returns true if the value is in the set. O(1).
185
     */
186
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
187
        return _contains(set._inner, value);
188
    }
189

                            
                        
190
    /**
191
     * @dev Returns the number of values in the set. O(1).
192
     */
193
    function length(Bytes32Set storage set) internal view returns (uint256) {
194
        return _length(set._inner);
195
    }
196

                            
                        
197
    /**
198
     * @dev Returns the value stored at position `index` in the set. O(1).
199
     *
200
     * Note that there are no guarantees on the ordering of values inside the
201
     * array, and it may change when more values are added or removed.
202
     *
203
     * Requirements:
204
     *
205
     * - `index` must be strictly less than {length}.
206
     */
207
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
208
        return _at(set._inner, index);
209
    }
210

                            
                        
211
    /**
212
     * @dev Return the entire set in an array
213
     *
214
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
215
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
216
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
217
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
218
     */
219
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
220
        bytes32[] memory store = _values(set._inner);
221
        bytes32[] memory result;
222

                            
                        
223
        /// @solidity memory-safe-assembly
224
        assembly {
225
            result := store
226
        }
227

                            
                        
228
        return result;
229
    }
230

                            
                        
231
    // AddressSet
232

                            
                        
233
    struct AddressSet {
234
        Set _inner;
235
    }
236

                            
                        
237
    /**
238
     * @dev Add a value to a set. O(1).
239
     *
240
     * Returns true if the value was added to the set, that is if it was not
241
     * already present.
242
     */
243
    function add(AddressSet storage set, address value) internal returns (bool) {
244
        return _add(set._inner, bytes32(uint256(uint160(value))));
245
    }
246

                            
                        
247
    /**
248
     * @dev Removes a value from a set. O(1).
249
     *
250
     * Returns true if the value was removed from the set, that is if it was
251
     * present.
252
     */
253
    function remove(AddressSet storage set, address value) internal returns (bool) {
254
        return _remove(set._inner, bytes32(uint256(uint160(value))));
255
    }
256

                            
                        
257
    /**
258
     * @dev Returns true if the value is in the set. O(1).
259
     */
260
    function contains(AddressSet storage set, address value) internal view returns (bool) {
261
        return _contains(set._inner, bytes32(uint256(uint160(value))));
262
    }
263

                            
                        
264
    /**
265
     * @dev Returns the number of values in the set. O(1).
266
     */
267
    function length(AddressSet storage set) internal view returns (uint256) {
268
        return _length(set._inner);
269
    }
270

                            
                        
271
    /**
272
     * @dev Returns the value stored at position `index` in the set. O(1).
273
     *
274
     * Note that there are no guarantees on the ordering of values inside the
275
     * array, and it may change when more values are added or removed.
276
     *
277
     * Requirements:
278
     *
279
     * - `index` must be strictly less than {length}.
280
     */
281
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
282
        return address(uint160(uint256(_at(set._inner, index))));
283
    }
284

                            
                        
285
    /**
286
     * @dev Return the entire set in an array
287
     *
288
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
289
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
290
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
291
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
292
     */
293
    function values(AddressSet storage set) internal view returns (address[] memory) {
294
        bytes32[] memory store = _values(set._inner);
295
        address[] memory result;
296

                            
                        
297
        /// @solidity memory-safe-assembly
298
        assembly {
299
            result := store
300
        }
301

                            
                        
302
        return result;
303
    }
304

                            
                        
305
    // UintSet
306

                            
                        
307
    struct UintSet {
308
        Set _inner;
309
    }
310

                            
                        
311
    /**
312
     * @dev Add a value to a set. O(1).
313
     *
314
     * Returns true if the value was added to the set, that is if it was not
315
     * already present.
316
     */
317
    function add(UintSet storage set, uint256 value) internal returns (bool) {
318
        return _add(set._inner, bytes32(value));
319
    }
320

                            
                        
321
    /**
322
     * @dev Removes a value from a set. O(1).
323
     *
324
     * Returns true if the value was removed from the set, that is if it was
325
     * present.
326
     */
327
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
328
        return _remove(set._inner, bytes32(value));
329
    }
330

                            
                        
331
    /**
332
     * @dev Returns true if the value is in the set. O(1).
333
     */
334
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
335
        return _contains(set._inner, bytes32(value));
336
    }
337

                            
                        
338
    /**
339
     * @dev Returns the number of values in the set. O(1).
340
     */
341
    function length(UintSet storage set) internal view returns (uint256) {
342
        return _length(set._inner);
343
    }
344

                            
                        
345
    /**
346
     * @dev Returns the value stored at position `index` in the set. O(1).
347
     *
348
     * Note that there are no guarantees on the ordering of values inside the
349
     * array, and it may change when more values are added or removed.
350
     *
351
     * Requirements:
352
     *
353
     * - `index` must be strictly less than {length}.
354
     */
355
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
356
        return uint256(_at(set._inner, index));
357
    }
358

                            
                        
359
    /**
360
     * @dev Return the entire set in an array
361
     *
362
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
363
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
364
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
365
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
366
     */
367
    function values(UintSet storage set) internal view returns (uint256[] memory) {
368
        bytes32[] memory store = _values(set._inner);
369
        uint256[] memory result;
370

                            
                        
371
        /// @solidity memory-safe-assembly
372
        assembly {
373
            result := store
374
        }
375

                            
                        
376
        return result;
377
    }
378
}
379

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
import "./IERC20.sol";
5

                            
                        
6
/**
7
 * Based on the stETH:
8
 *  -   https://docs.lido.fi/contracts/lido#
9
 */
10
interface ICollateralToken is IERC20 {
11
    // Returns the amount of shares that corresponds to _ethAmount protocol-controlled Ether
12
    function getSharesByPooledEth(uint256 _ethAmount) external view returns (uint256);
13

                            
                        
14
    // Returns the amount of Ether that corresponds to _sharesAmount token shares
15
    function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256);
16

                            
                        
17
    // Moves `_sharesAmount` token shares from the caller's account to the `_recipient` account.
18
    function transferShares(address _recipient, uint256 _sharesAmount) external returns (uint256);
19

                            
                        
20
    // Returns the amount of shares owned by _account
21
    function sharesOf(address _account) external view returns (uint256);
22

                            
                        
23
    // Returns authorized oracle address
24
    function getOracle() external view returns (address);
25
}
26

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
/**
5
 * Based on the stETH:
6
 *  -   https://docs.lido.fi/contracts/lido#
7
 */
8
interface ICollateralTokenOracle {
9
    // Return beacon specification data.
10
    function getBeaconSpec()
11
        external
12
        view
13
        returns (
14
            uint64 epochsPerFrame,
15
            uint64 slotsPerEpoch,
16
            uint64 secondsPerSlot,
17
            uint64 genesisTime
18
        );
19
}
20

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
/**
6
 * Based on the OpenZeppelin IER20 interface:
7
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol
8
 *
9
 * @dev Interface of the ERC20 standard as defined in the EIP.
10
 */
11
interface IERC20 {
12
    /**
13
     * @dev Returns the amount of tokens in existence.
14
     */
15
    function totalSupply() external view returns (uint256);
16

                            
                        
17
    /**
18
     * @dev Returns the amount of tokens owned by `account`.
19
     */
20
    function balanceOf(address account) external view returns (uint256);
21

                            
                        
22
    /**
23
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
24
     *
25
     * Returns a boolean value indicating whether the operation succeeded.
26
     *
27
     * Emits a {Transfer} event.
28
     */
29
    function transfer(address recipient, uint256 amount) external returns (bool);
30

                            
                        
31
    /**
32
     * @dev Returns the remaining number of tokens that `spender` will be
33
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
34
     * zero by default.
35
     *
36
     * This value changes when {approve} or {transferFrom} are called.
37
     */
38
    function allowance(address owner, address spender) external view returns (uint256);
39

                            
                        
40
    function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
41

                            
                        
42
    function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
43

                            
                        
44
    /**
45
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
46
     *
47
     * Returns a boolean value indicating whether the operation succeeded.
48
     *
49
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
50
     * that someone may use both the old and the new allowance by unfortunate
51
     * transaction ordering. One possible solution to mitigate this race
52
     * condition is to first reduce the spender's allowance to 0 and set the
53
     * desired value afterwards:
54
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
55
     *
56
     * Emits an {Approval} event.
57
     */
58
    function approve(address spender, uint256 amount) external returns (bool);
59

                            
                        
60
    /**
61
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
62
     * allowance mechanism. `amount` is then deducted from the caller's
63
     * allowance.
64
     *
65
     * Returns a boolean value indicating whether the operation succeeded.
66
     *
67
     * Emits a {Transfer} event.
68
     */
69
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
70

                            
                        
71
    function name() external view returns (string memory);
72

                            
                        
73
    function symbol() external view returns (string memory);
74

                            
                        
75
    function decimals() external view returns (uint8);
76

                            
                        
77
    /**
78
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
79
     * another (`to`).
80
     *
81
     * Note that `value` may be zero.
82
     */
83
    event Transfer(address indexed from, address indexed to, uint256 value);
84

                            
                        
85
    /**
86
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
87
     * a call to {approve}. `value` is the new allowance.
88
     */
89
    event Approval(address indexed owner, address indexed spender, uint256 value);
90
}
91

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
/**
6
 * @dev Interface of the ERC2612 standard as defined in the EIP.
7
 *
8
 * Adds the {permit} method, which can be used to change one's
9
 * {IERC20-allowance} without having to send a transaction, by signing a
10
 * message. This allows users to spend tokens without having to hold Ether.
11
 *
12
 * See https://eips.ethereum.org/EIPS/eip-2612.
13
 *
14
 * Code adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/
15
 */
16
interface IERC2612 {
17
    /**
18
     * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens,
19
     * given `owner`'s signed approval.
20
     *
21
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
22
     * ordering also apply here.
23
     *
24
     * Emits an {Approval} event.
25
     *
26
     * Requirements:
27
     *
28
     * - `owner` cannot be the zero address.
29
     * - `spender` cannot be the zero address.
30
     * - `deadline` must be a timestamp in the future.
31
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
32
     * over the EIP712-formatted function arguments.
33
     * - the signature must use ``owner``'s current nonce (see {nonces}).
34
     *
35
     * For more information on the signature format, see the
36
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
37
     * section].
38
     */
39
    function permit(
40
        address owner,
41
        address spender,
42
        uint256 amount,
43
        uint256 deadline,
44
        uint8 v,
45
        bytes32 r,
46
        bytes32 s
47
    ) external;
48

                            
                        
49
    /**
50
     * @dev Returns the current ERC2612 nonce for `owner`. This value must be
51
     * included whenever a signature is generated for {permit}.
52
     *
53
     * Every successful call to {permit} increases `owner`'s nonce by one. This
54
     * prevents a signature from being used multiple times.
55
     *
56
     * `owner` can limit the time a Permit is valid for by setting `deadline` to
57
     * a value in the near future. The deadline argument can be set to uint256(-1) to
58
     * create Permits that effectively never expire.
59
     */
60
    function nonces(address owner) external view returns (uint256);
61

                            
                        
62
    function version() external view returns (string memory);
63

                            
                        
64
    function permitTypeHash() external view returns (bytes32);
65

                            
                        
66
    function domainSeparator() external view returns (bytes32);
67
}
68

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import "./EnumerableSet.sol";
5

                            
                        
6
/// @notice Role based Authority that supports up to 256 roles.
7
/// @author BadgerDAO
8
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol)
9
/// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol)
10
interface IRolesAuthority {
11
    event UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled);
12

                            
                        
13
    event PublicCapabilityUpdated(address indexed target, bytes4 indexed functionSig, bool enabled);
14
    event CapabilityBurned(address indexed target, bytes4 indexed functionSig);
15

                            
                        
16
    event RoleCapabilityUpdated(
17
        uint8 indexed role,
18
        address indexed target,
19
        bytes4 indexed functionSig,
20
        bool enabled
21
    );
22

                            
                        
23
    enum CapabilityFlag {
24
        None,
25
        Public,
26
        Burned
27
    }
28
}
29

                            
                        

Lines covered: 32 / 37 (86.5%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./BaseMath.sol";
6
import "./LiquityMath.sol";
7
import "../Interfaces/IActivePool.sol";
8
import "../Interfaces/IPriceFeed.sol";
9
import "../Interfaces/ILiquityBase.sol";
10
import "../Dependencies/ICollateralToken.sol";
11

                            
                        
12
/*
13
 * Base contract for CdpManager, BorrowerOperations. Contains global system constants and
14
 * common functions.
15
 */
16
contract LiquityBase is BaseMath, ILiquityBase {
17
    // Collateral Ratio applied for Liquidation Incentive
18
    // i.e., liquidator repay $1 worth of debt to get back $1.03 worth of collateral
19
    uint256 public constant LICR = 1030000000000000000; // 103%
20

                            
                        
21
    // Minimum collateral ratio for individual cdps
22
    uint256 public constant MCR = 1100000000000000000; // 110%
23

                            
                        
24
    // Critical system collateral ratio. If the system's total collateral ratio (TCR) falls below the CCR, Recovery Mode is triggered.
25
    uint256 public constant CCR = 1250000000000000000; // 125%
26

                            
                        
27
    // Amount of stETH collateral to be locked in active pool on opening cdps
28
    uint256 public constant LIQUIDATOR_REWARD = 2e17;
29

                            
                        
30
    // Minimum amount of stETH collateral a CDP must have
31
    uint256 public constant MIN_NET_COLL = 2e18;
32

                            
                        
33
    uint256 public constant PERCENT_DIVISOR = 200; // dividing by 200 yields 0.5%
34

                            
                        
35
    uint256 public constant BORROWING_FEE_FLOOR = 0; // 0.5%
36

                            
                        
37
    uint256 public constant STAKING_REWARD_SPLIT = 5_000; // taking 50% cut from staking reward
38

                            
                        
39
    uint256 public constant MAX_REWARD_SPLIT = 10_000;
40

                            
                        
41
    IActivePool public immutable activePool;
42

                            
                        
43
    IPriceFeed public immutable override priceFeed;
44

                            
                        
45
    // the only collateral token allowed in CDP
46
    ICollateralToken public immutable collateral;
47

                            
                        
48
    constructor(address _activePoolAddress, address _priceFeedAddress, address _collateralAddress) {
49
        activePool = IActivePool(_activePoolAddress);
50
        priceFeed = IPriceFeed(_priceFeedAddress);
51
        collateral = ICollateralToken(_collateralAddress);
52
    }
53

                            
                        
54
    // --- Gas compensation functions ---
55

                            
                        
56
    function _getNetColl(uint256 _coll) internal pure returns (uint256) {
57
        return _coll - LIQUIDATOR_REWARD;
58
    }
59

                            
                        
60
    /**
61
        @notice Get the entire system collateral
62
        @notice Entire system collateral = collateral stored in ActivePool, using their internal accounting
63
        @dev Coll stored for liquidator rewards or coll in CollSurplusPool are not included
64
     */
65
    function getSystemCollShares() public view returns (uint256 entireSystemColl) {
66
        return (activePool.getSystemCollShares());
67
    }
68

                            
                        
69
    /**
70
        @notice Get the entire system debt
71
        @notice Entire system collateral = collateral stored in ActivePool, using their internal accounting
72
     */
73
    function _getSystemDebt() internal view returns (uint256 entireSystemDebt) {
74
        return (activePool.getSystemDebt());
75
    }
76

                            
                        
77
    function _getTCR(uint256 _price) internal view returns (uint256 TCR) {
78
        (TCR, , ) = _getTCRWithSystemDebtAndCollShares(_price);
79
    }
80

                            
                        
81
    function _getTCRWithSystemDebtAndCollShares(
82
        uint256 _price
83
    ) internal view returns (uint256 TCR, uint256 _coll, uint256 _debt) {
84
        uint256 entireSystemColl = getSystemCollShares();
85
        uint256 entireSystemDebt = _getSystemDebt();
86

                            
                        
87
        uint256 _underlyingCollateral = collateral.getPooledEthByShares(entireSystemColl);
88
        TCR = LiquityMath._computeCR(_underlyingCollateral, entireSystemDebt, _price);
89

                            
                        
90
        return (TCR, entireSystemColl, entireSystemDebt);
91
    }
92

                            
                        
93
    function _checkRecoveryMode(uint256 _price) internal view returns (bool) {
94
        return _checkRecoveryModeForTCR(_getTCR(_price));
95
    }
96

                            
                        
97
    function _checkRecoveryModeForTCR(uint256 _tcr) internal view returns (bool) {
98
        return _tcr < CCR;
99
    }
100

                            
                        
101
    function _requireUserAcceptsFee(
102
        uint256 _fee,
103
        uint256 _amount,
104
        uint256 _maxFeePercentage
105
    ) internal pure {
106
        uint256 feePercentage = (_fee * DECIMAL_PRECISION) / _amount;
107
        require(feePercentage <= _maxFeePercentage, "Fee exceeded provided maximum");
108
    }
109

                            
                        
110
    // Convert debt denominated in ETH to debt denominated in BTC given that _price is ETH/BTC
111
    // _debt is denominated in ETH
112
    // _price is ETH/BTC
113
    function _convertDebtDenominationToBtc(
114
        uint256 _debt,
115
        uint256 _price
116
    ) internal pure returns (uint256) {
117
        return (_debt * _price) / DECIMAL_PRECISION;
118
    }
119
}
120

                            
                        

Lines covered: 32 / 36 (88.9%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
library LiquityMath {
6
    uint256 internal constant DECIMAL_PRECISION = 1e18;
7
    uint256 public constant MAX_TCR = type(uint256).max;
8

                            
                        
9
    /* Precision for Nominal ICR (independent of price). Rationale for the value:
10
     *
11
     * - Making it “too high” could lead to overflows.
12
     * - Making it “too low” could lead to an ICR equal to zero, due to truncation from Solidity floor division.
13
     *
14
     * This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 ETH,
15
     * and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.
16
     *
17
     */
18
    uint256 internal constant NICR_PRECISION = 1e20;
19

                            
                        
20
    function _min(uint256 _a, uint256 _b) internal pure returns (uint256) {
21
        return (_a < _b) ? _a : _b;
22
    }
23

                            
                        
24
    function _max(uint256 _a, uint256 _b) internal pure returns (uint256) {
25
        return (_a >= _b) ? _a : _b;
26
    }
27

                            
                        
28
    /*
29
     * Multiply two decimal numbers and use normal rounding rules:
30
     * -round product up if 19'th mantissa digit >= 5
31
     * -round product down if 19'th mantissa digit < 5
32
     *
33
     * Used only inside the exponentiation, _decPow().
34
     */
35
    function decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) {
36
        uint256 prod_xy = x * y;
37

                            
                        
38
        decProd = (prod_xy + (DECIMAL_PRECISION / 2)) / DECIMAL_PRECISION;
39
    }
40

                            
                        
41
    /*
42
     * _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n.
43
     *
44
     * Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity.
45
     *
46
     * Called by two functions that represent time in units of minutes:
47
     * 1) CdpManager._calcDecayedBaseRate
48
     * 2) CommunityIssuance._getCumulativeIssuanceFraction
49
     *
50
     * The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals
51
     * "minutes in 1000 years": 60 * 24 * 365 * 1000
52
     *
53
     * If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be
54
     * negligibly different from just passing the cap, since:
55
     *
56
     * In function 1), the decayed base rate will be 0 for 1000 years or > 1000 years
57
     * In function 2), the difference in tokens issued at 1000 years and any time > 1000 years, will be negligible
58
     */
59
    function _decPow(uint256 _base, uint256 _minutes) internal pure returns (uint256) {
60
        if (_minutes > 525600000) {
61
            _minutes = 525600000;
62
        } // cap to avoid overflow
63

                            
                        
64
        if (_minutes == 0) {
65
            return DECIMAL_PRECISION;
66
        }
67

                            
                        
68
        uint256 y = DECIMAL_PRECISION;
69
        uint256 x = _base;
70
        uint256 n = _minutes;
71

                            
                        
72
        // Exponentiation-by-squaring
73
        while (n > 1) {
74
            if (n % 2 == 0) {
75
                x = decMul(x, x);
76
                n = n / 2;
77
            } else {
78
                // if (n % 2 != 0)
79
                y = decMul(x, y);
80
                x = decMul(x, x);
81
                n = (n - 1) / 2;
82
            }
83
        }
84

                            
                        
85
        return decMul(x, y);
86
    }
87

                            
                        
88
    function _getAbsoluteDifference(uint256 _a, uint256 _b) internal pure returns (uint256) {
89
        return (_a >= _b) ? (_a - _b) : (_b - _a);
90
    }
91

                            
                        
92
    function _computeNominalCR(uint256 _coll, uint256 _debt) internal pure returns (uint256) {
93
        if (_debt > 0) {
94
            return (_coll * NICR_PRECISION) / _debt;
95
        }
96
        // Return the maximal value for uint256 if the Cdp has a debt of 0. Represents "infinite" CR.
97
        else {
98
            // if (_debt == 0)
99
            return MAX_TCR;
100
        }
101
    }
102

                            
                        
103
    function _computeCR(
104
        uint256 _coll,
105
        uint256 _debt,
106
        uint256 _price
107
    ) internal pure returns (uint256) {
108
        if (_debt > 0) {
109
            uint256 newCollRatio = (_coll * _price) / _debt;
110

                            
                        
111
            return newCollRatio;
112
        }
113
        // Return the maximal value for uint256 if the Cdp has a debt of 0. Represents "infinite" CR.
114
        else {
115
            // if (_debt == 0)
116
            return MAX_TCR;
117
        }
118
    }
119
}
120

                            
                        

Lines covered: 4 / 11 (36.4%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
import "./Context.sol";
7

                            
                        
8
/**
9
 * @dev Contract module which provides a basic access control mechanism, where
10
 * there is an account (an owner) that can be granted exclusive access to
11
 * specific functions.
12
 *
13
 * By default, the owner account will be the one that deploys the contract. This
14
 * can later be changed with {transferOwnership}.
15
 *
16
 * This module is used through inheritance. It will make available the modifier
17
 * `onlyOwner`, which can be applied to your functions to restrict their use to
18
 * the owner.
19
 */
20
abstract contract Ownable is Context {
21
    address private _owner;
22

                            
                        
23
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
24

                            
                        
25
    /**
26
     * @dev Initializes the contract setting the deployer as the initial owner.
27
     */
28
    constructor() {
29
        _transferOwnership(_msgSender());
30
    }
31

                            
                        
32
    /**
33
     * @dev Throws if called by any account other than the owner.
34
     */
35
    modifier onlyOwner() {
36
        _checkOwner();
37
        _;
38
    }
39

                            
                        
40
    /**
41
     * @dev Returns the address of the current owner.
42
     */
43
    function owner() public view virtual returns (address) {
44
        return _owner;
45
    }
46

                            
                        
47
    /**
48
     * @dev Throws if the sender is not the owner.
49
     */
50
    function _checkOwner() internal view virtual {
51
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
52
    }
53

                            
                        
54
    /**
55
     * @dev Leaves the contract without owner. It will not be possible to call
56
     * `onlyOwner` functions anymore. Can only be called by the current owner.
57
     *
58
     * NOTE: Renouncing ownership will leave the contract without an owner,
59
     * thereby removing any functionality that is only available to the owner.
60
     */
61
    function renounceOwnership() public virtual onlyOwner {
62
        _transferOwnership(address(0));
63
    }
64

                            
                        
65
    /**
66
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
67
     * Can only be called by the current owner.
68
     */
69
    function transferOwnership(address newOwner) public virtual onlyOwner {
70
        require(newOwner != address(0), "Ownable: new owner is the zero address");
71
        _transferOwnership(newOwner);
72
    }
73

                            
                        
74
    /**
75
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
76
     * Internal function without access restriction.
77
     */
78
    function _transferOwnership(address newOwner) internal virtual {
79
        address oldOwner = _owner;
80
        _owner = newOwner;
81
        emit OwnershipTransferred(oldOwner, newOwner);
82
    }
83
}
84

                            
                        

Lines covered: 0 / 5 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4
import "../Interfaces/IPermitNonce.sol";
5

                            
                        
6
/**
7
 * @dev This abstract contract provides a mapping from address to nonce (uint256) used for permit signature
8
 */
9
contract PermitNonce is IPermitNonce {
10
    mapping(address => uint256) internal _nonces;
11

                            
                        
12
    /// @dev Increase current nonce for msg.sender by one.
13
    /// @notice This function could be used to invalidate any signed permit out there
14
    function increasePermitNonce() external returns (uint256) {
15
        return ++_nonces[msg.sender];
16
    }
17

                            
                        
18
    /// @dev Return current nonce for msg.sender fOR EIP-2612 compatibility
19
    function nonces(address owner) external view virtual returns (uint256) {
20
        return _nonces[owner];
21
    }
22
}
23

                            
                        

Lines covered: 7 / 8 (87.5%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
/**
7
 * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
8
 * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
9
 * be specified by overriding the virtual {_implementation} function.
10
 *
11
 * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
12
 * different contract through the {_delegate} function.
13
 *
14
 * The success and return data of the delegated call will be returned back to the caller of the proxy.
15
 * @dev BadgerDAO: Simplified to the core delegation functionality, without any additional features.
16
 */
17
contract Proxy {
18
    /**
19
     * @dev Delegates the current call to `implementation`.
20
     *
21
     * This function does not return to its internal call site, it will return directly to the external caller.
22
     */
23
    function _delegate(address implementation) internal virtual {
24
        assembly {
25
            // Copy msg.data. We take full control of memory in this inline assembly
26
            // block because it will not return to Solidity code. We overwrite the
27
            // Solidity scratch pad at memory position 0.
28
            calldatacopy(0, 0, calldatasize())
29

                            
                        
30
            // Call the implementation.
31
            // out and outsize are 0 because we don't know the size yet.
32
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
33

                            
                        
34
            // Copy the returned data.
35
            returndatacopy(0, 0, returndatasize())
36

                            
                        
37
            switch result
38
            // delegatecall returns 0 on error.
39
            case 0 {
40
                revert(0, returndatasize())
41
            }
42
            default {
43
                return(0, returndatasize())
44
            }
45
        }
46
    }
47
}
48

                            
                        

Lines covered: 3 / 6 (50.0%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
/// @notice Gas optimized reentrancy protection for smart contracts.
5
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
6
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
7
abstract contract ReentrancyGuard {
8
    uint256 internal constant OPEN = 1;
9
    uint256 internal constant LOCKED = 2;
10

                            
                        
11
    uint256 public locked = OPEN;
12

                            
                        
13
    modifier nonReentrant() virtual {
14
        require(locked == OPEN, "ReentrancyGuard: Reentrancy in nonReentrant call");
15

                            
                        
16
        locked = LOCKED;
17

                            
                        
18
        _;
19

                            
                        
20
        locked = OPEN;
21
    }
22
}
23

                            
                        

Lines covered: 17 / 43 (39.5%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import {IRolesAuthority} from "./IRolesAuthority.sol";
5
import {Auth, Authority} from "./Auth.sol";
6
import "./EnumerableSet.sol";
7

                            
                        
8
/// @notice Role based Authority that supports up to 256 roles.
9
/// @author BadgerDAO
10
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol)
11
/// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol)
12
contract RolesAuthority is IRolesAuthority, Auth, Authority {
13
    using EnumerableSet for EnumerableSet.Bytes32Set;
14
    using EnumerableSet for EnumerableSet.AddressSet;
15

                            
                        
16
    /*//////////////////////////////////////////////////////////////
17
                               CONSTRUCTOR
18
    //////////////////////////////////////////////////////////////*/
19

                            
                        
20
    constructor(address _owner, Authority _authority) Auth(_owner, _authority) {}
21

                            
                        
22
    /*//////////////////////////////////////////////////////////////
23
                            ROLE/USER STORAGE
24
    //////////////////////////////////////////////////////////////*/
25

                            
                        
26
    EnumerableSet.AddressSet internal users;
27
    EnumerableSet.AddressSet internal targets;
28
    mapping(address => EnumerableSet.Bytes32Set) internal enabledFunctionSigsByTarget;
29

                            
                        
30
    EnumerableSet.Bytes32Set internal enabledFunctionSigsPublic;
31

                            
                        
32
    mapping(address => bytes32) public getUserRoles;
33

                            
                        
34
    mapping(address => mapping(bytes4 => CapabilityFlag)) public capabilityFlag;
35

                            
                        
36
    mapping(address => mapping(bytes4 => bytes32)) public getRolesWithCapability;
37

                            
                        
38
    function doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) {
39
        return (uint256(getUserRoles[user]) >> role) & 1 != 0;
40
    }
41

                            
                        
42
    function doesRoleHaveCapability(
43
        uint8 role,
44
        address target,
45
        bytes4 functionSig
46
    ) public view virtual returns (bool) {
47
        return (uint256(getRolesWithCapability[target][functionSig]) >> role) & 1 != 0;
48
    }
49

                            
                        
50
    function isPublicCapability(address target, bytes4 functionSig) public view returns (bool) {
51
        return capabilityFlag[target][functionSig] == CapabilityFlag.Public;
52
    }
53

                            
                        
54
    /*//////////////////////////////////////////////////////////////
55
                           AUTHORIZATION LOGIC
56
    //////////////////////////////////////////////////////////////*/
57

                            
                        
58
    /**
59
        @notice A user can call a given function signature on a given target address if:
60
            - The capability has not been burned
61
            - That capability is public, or the user has a role that has been granted the capability to call the function
62
     */
63
    function canCall(
64
        address user,
65
        address target,
66
        bytes4 functionSig
67
    ) public view virtual override returns (bool) {
68
        CapabilityFlag flag = capabilityFlag[target][functionSig];
69

                            
                        
70
        if (flag == CapabilityFlag.Burned) {
71
            return false;
72
        } else if (flag == CapabilityFlag.Public) {
73
            return true;
74
        } else {
75
            return bytes32(0) != getUserRoles[user] & getRolesWithCapability[target][functionSig];
76
        }
77
    }
78

                            
                        
79
    /*//////////////////////////////////////////////////////////////
80
                   ROLE CAPABILITY CONFIGURATION LOGIC
81
    //////////////////////////////////////////////////////////////*/
82

                            
                        
83
    /// @notice Set a capability flag as public, meaning any account can call it. Or revoke this capability.
84
    /// @dev A capability cannot be made public if it has been burned.
85
    function setPublicCapability(
86
        address target,
87
        bytes4 functionSig,
88
        bool enabled
89
    ) public virtual requiresAuth {
90
        require(
91
            capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
92
            "RolesAuthority: Capability Burned"
93
        );
94

                            
                        
95
        if (enabled) {
96
            capabilityFlag[target][functionSig] = CapabilityFlag.Public;
97
        } else {
98
            capabilityFlag[target][functionSig] = CapabilityFlag.None;
99
        }
100

                            
                        
101
        emit PublicCapabilityUpdated(target, functionSig, enabled);
102
    }
103

                            
                        
104
    /// @notice Grant a specified role the ability to call a function on a target.
105
    /// @notice Has no effect
106
    function setRoleCapability(
107
        uint8 role,
108
        address target,
109
        bytes4 functionSig,
110
        bool enabled
111
    ) public virtual requiresAuth {
112
        if (enabled) {
113
            getRolesWithCapability[target][functionSig] |= bytes32(1 << role);
114
            enabledFunctionSigsByTarget[target].add(bytes32(functionSig));
115

                            
                        
116
            if (!targets.contains(target)) {
117
                targets.add(target);
118
            }
119
        } else {
120
            getRolesWithCapability[target][functionSig] &= ~bytes32(1 << role);
121
            enabledFunctionSigsByTarget[target].remove(bytes32(functionSig));
122

                            
                        
123
            // If no enabled function signatures exist for this target, remove target
124
            if (enabledFunctionSigsByTarget[target].length() == 0) {
125
                targets.remove(target);
126
            }
127
        }
128

                            
                        
129
        emit RoleCapabilityUpdated(role, target, functionSig, enabled);
130
    }
131

                            
                        
132
    /// @notice Permanently burns a capability for a target.
133
    function burnCapability(address target, bytes4 functionSig) public virtual requiresAuth {
134
        require(
135
            capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
136
            "RolesAuthority: Capability Burned"
137
        );
138
        capabilityFlag[target][functionSig] = CapabilityFlag.Burned;
139

                            
                        
140
        emit CapabilityBurned(target, functionSig);
141
    }
142

                            
                        
143
    /*//////////////////////////////////////////////////////////////
144
                       USER ROLE ASSIGNMENT LOGIC
145
    //////////////////////////////////////////////////////////////*/
146

                            
                        
147
    function setUserRole(address user, uint8 role, bool enabled) public virtual requiresAuth {
148
        if (enabled) {
149
            getUserRoles[user] |= bytes32(1 << role);
150

                            
                        
151
            if (!users.contains(user)) {
152
                users.add(user);
153
            }
154
        } else {
155
            getUserRoles[user] &= ~bytes32(1 << role);
156

                            
                        
157
            // Remove user if no more roles
158
            if (getUserRoles[user] == bytes32(0)) {
159
                users.remove(user);
160
            }
161
        }
162

                            
                        
163
        emit UserRoleUpdated(user, role, enabled);
164
    }
165
}
166

                            
                        

Lines covered: 0 / 5 (0.0%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
import "./IERC20.sol";
7
import "./Address.sol";
8

                            
                        
9
/**
10
 * @title SafeERC20
11
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
12
 * contract returns false). Tokens that return no value (and instead revert or
13
 * throw on failure) are also supported, non-reverting calls are assumed to be
14
 * successful.
15
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
16
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
17
 */
18
library SafeERC20 {
19
    using Address for address;
20

                            
                        
21
    /**
22
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
23
     * non-reverting calls are assumed to be successful.
24
     */
25
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
26
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
27
    }
28

                            
                        
29
    /// @dev Calls approve while checking bool return value, handles no-return tokens
30
    function safeApprove(IERC20 token, address spender, uint256 amount) internal {
31
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, amount));
32
    }
33

                            
                        
34
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
35
        _callOptionalReturn(
36
            token,
37
            abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
38
        );
39
    }
40

                            
                        
41
    /**
42
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
43
     * on the return value: the return value is optional (but if data is returned, it must not be false).
44
     * @param token The token targeted by the call.
45
     * @param data The call data (encoded using abi.encode or one of its variants).
46
     */
47
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
48
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
49
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
50
        // the target address contains contract code and also asserts for success in the low-level call.
51

                            
                        
52
        bytes memory returndata = address(token).functionCall(
53
            data,
54
            "SafeERC20: low-level call failed"
55
        );
56
        require(
57
            returndata.length == 0 || abi.decode(returndata, (bool)),
58
            "SafeERC20: ERC20 operation did not succeed"
59
        );
60
    }
61
}
62

                            
                        

Lines covered: 0 / 1 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
/**
6
 * Based on OpenZeppelin's SafeMath:
7
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol
8
 *
9
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
10
 * checks.
11
 *
12
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
13
 * in bugs, because programmers usually assume that an overflow raises an
14
 * error, which is the standard behavior in high level programming languages.
15
 * `SafeMath` restores this intuition by reverting the transaction when an
16
 * operation overflows.
17
 *
18
 * Using this library instead of the unchecked operations eliminates an entire
19
 * class of bugs, so it's recommended to use it always.
20
 */
21
library SafeMath {
22
    /**
23
     * @dev Returns the addition of two unsigned integers, reverting on
24
     * overflow.
25
     *
26
     * Counterpart to Solidity's `+` operator.
27
     *
28
     * Requirements:
29
     * - Addition cannot overflow.
30
     */
31
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
32
        uint256 c = a + b;
33
        require(c >= a, "SafeMath: addition overflow");
34

                            
                        
35
        return c;
36
    }
37

                            
                        
38
    /**
39
     * @dev Returns the subtraction of two unsigned integers, reverting on
40
     * overflow (when the result is negative).
41
     *
42
     * Counterpart to Solidity's `-` operator.
43
     *
44
     * Requirements:
45
     * - Subtraction cannot overflow.
46
     */
47
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
48
        return sub(a, b, "SafeMath: subtraction overflow");
49
    }
50

                            
                        
51
    /**
52
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
53
     * overflow (when the result is negative).
54
     *
55
     * Counterpart to Solidity's `-` operator.
56
     *
57
     * Requirements:
58
     * - Subtraction cannot overflow.
59
     *
60
     * _Available since v2.4.0._
61
     */
62
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
63
        require(b <= a, errorMessage);
64
        uint256 c = a - b;
65

                            
                        
66
        return c;
67
    }
68

                            
                        
69
    /**
70
     * @dev Returns the multiplication of two unsigned integers, reverting on
71
     * overflow.
72
     *
73
     * Counterpart to Solidity's `*` operator.
74
     *
75
     * Requirements:
76
     * - Multiplication cannot overflow.
77
     */
78
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
79
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
80
        // benefit is lost if 'b' is also tested.
81
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
82
        if (a == 0) {
83
            return 0;
84
        }
85

                            
                        
86
        uint256 c = a * b;
87
        require(c / a == b, "SafeMath: multiplication overflow");
88

                            
                        
89
        return c;
90
    }
91

                            
                        
92
    /**
93
     * @dev Returns the integer division of two unsigned integers. Reverts on
94
     * division by zero. The result is rounded towards zero.
95
     *
96
     * Counterpart to Solidity's `/` operator. Note: this function uses a
97
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
98
     * uses an invalid opcode to revert (consuming all remaining gas).
99
     *
100
     * Requirements:
101
     * - The divisor cannot be zero.
102
     */
103
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
104
        return div(a, b, "SafeMath: division by zero");
105
    }
106

                            
                        
107
    /**
108
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
109
     * division by zero. The result is rounded towards zero.
110
     *
111
     * Counterpart to Solidity's `/` operator. Note: this function uses a
112
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
113
     * uses an invalid opcode to revert (consuming all remaining gas).
114
     *
115
     * Requirements:
116
     * - The divisor cannot be zero.
117
     *
118
     * _Available since v2.4.0._
119
     */
120
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
121
        // Solidity only automatically asserts when dividing by 0
122
        require(b > 0, errorMessage);
123
        uint256 c = a / b;
124
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
125

                            
                        
126
        return c;
127
    }
128

                            
                        
129
    /**
130
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
131
     * Reverts when dividing by zero.
132
     *
133
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
134
     * opcode (which leaves remaining gas untouched) while Solidity uses an
135
     * invalid opcode to revert (consuming all remaining gas).
136
     *
137
     * Requirements:
138
     * - The divisor cannot be zero.
139
     */
140
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
141
        return mod(a, b, "SafeMath: modulo by zero");
142
    }
143

                            
                        
144
    /**
145
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
146
     * Reverts with custom message when dividing by zero.
147
     *
148
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
149
     * opcode (which leaves remaining gas untouched) while Solidity uses an
150
     * invalid opcode to revert (consuming all remaining gas).
151
     *
152
     * Requirements:
153
     * - The divisor cannot be zero.
154
     *
155
     * _Available since v2.4.0._
156
     */
157
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
158
        require(b != 0, errorMessage);
159
        return a % b;
160
    }
161
}
162

                            
                        

Lines covered: 31 / 43 (72.1%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Dependencies/Create3.sol";
6
import "./Dependencies/Ownable.sol";
7

                            
                        
8
contract EBTCDeployer is Ownable {
9
    string public constant name = "eBTC Deployer";
10

                            
                        
11
    string public constant AUTHORITY = "ebtc.v1.authority";
12
    string public constant LIQUIDATION_LIBRARY = "ebtc.v1.liquidationLibrary";
13
    string public constant CDP_MANAGER = "ebtc.v1.cdpManager";
14
    string public constant BORROWER_OPERATIONS = "ebtc.v1.borrowerOperations";
15

                            
                        
16
    string public constant PRICE_FEED = "ebtc.v1.priceFeed";
17
    string public constant SORTED_CDPS = "ebtc.v1.sortedCdps";
18

                            
                        
19
    string public constant ACTIVE_POOL = "ebtc.v1.activePool";
20
    string public constant COLL_SURPLUS_POOL = "ebtc.v1.collSurplusPool";
21

                            
                        
22
    string public constant HINT_HELPERS = "ebtc.v1.hintHelpers";
23
    string public constant EBTC_TOKEN = "ebtc.v1.eBTCToken";
24
    string public constant FEE_RECIPIENT = "ebtc.v1.feeRecipient";
25
    string public constant MULTI_CDP_GETTER = "ebtc.v1.multiCdpGetter";
26

                            
                        
27
    event ContractDeployed(address indexed contractAddress, string contractName, bytes32 salt);
28

                            
                        
29
    struct EbtcAddresses {
30
        address authorityAddress;
31
        address liquidationLibraryAddress;
32
        address cdpManagerAddress;
33
        address borrowerOperationsAddress;
34
        address priceFeedAddress;
35
        address sortedCdpsAddress;
36
        address activePoolAddress;
37
        address collSurplusPoolAddress;
38
        address hintHelpersAddress;
39
        address ebtcTokenAddress;
40
        address feeRecipientAddress;
41
        address multiCdpGetterAddress;
42
    }
43

                            
                        
44
    /**
45
    @notice Helper method to return a set of future addresses for eBTC. Intended to be used in the order specified.
46
    
47
    @dev The order is as follows:
48
    0: authority
49
    1: liquidationLibrary
50
    2: cdpManager
51
    3: borrowerOperations
52
    4: priceFeed
53
    5; sortedCdps
54
    6: activePool
55
    7: collSurplusPool
56
    8: hintHelpers
57
    9: eBTCToken
58
    10: feeRecipient
59
    11: multiCdpGetter
60

                            
                        
61

                            
                        
62
     */
63
    function getFutureEbtcAddresses() public view returns (EbtcAddresses memory) {
64
        EbtcAddresses memory addresses = EbtcAddresses(
65
            Create3.addressOf(keccak256(abi.encodePacked(AUTHORITY))),
66
            Create3.addressOf(keccak256(abi.encodePacked(LIQUIDATION_LIBRARY))),
67
            Create3.addressOf(keccak256(abi.encodePacked(CDP_MANAGER))),
68
            Create3.addressOf(keccak256(abi.encodePacked(BORROWER_OPERATIONS))),
69
            Create3.addressOf(keccak256(abi.encodePacked(PRICE_FEED))),
70
            Create3.addressOf(keccak256(abi.encodePacked(SORTED_CDPS))),
71
            Create3.addressOf(keccak256(abi.encodePacked(ACTIVE_POOL))),
72
            Create3.addressOf(keccak256(abi.encodePacked(COLL_SURPLUS_POOL))),
73
            Create3.addressOf(keccak256(abi.encodePacked(HINT_HELPERS))),
74
            Create3.addressOf(keccak256(abi.encodePacked(EBTC_TOKEN))),
75
            Create3.addressOf(keccak256(abi.encodePacked(FEE_RECIPIENT))),
76
            Create3.addressOf(keccak256(abi.encodePacked(MULTI_CDP_GETTER)))
77
        );
78

                            
                        
79
        return addresses;
80
    }
81

                            
                        
82
    /**
83
        @notice Deploy a contract using salt in string format and arbitrary runtime code.
84
        @dev Intended use is: get the future eBTC addresses, then deploy the appropriate contract to each address via this method, building the constructor using the mapped addresses
85
        @dev no enforcment of bytecode at address as we can't know the runtime code in this contract due to space constraints
86
        @dev gated to given deployer EOA to ensure no interference with process, given proper actions by deployer
87
     */
88
    function deploy(
89
        string memory _saltString,
90
        bytes memory _creationCode
91
    ) public returns (address deployedAddress) {
92
        bytes32 _salt = keccak256(abi.encodePacked(_saltString));
93
        deployedAddress = Create3.create3(_salt, _creationCode);
94
        emit ContractDeployed(deployedAddress, _saltString, _salt);
95
    }
96

                            
                        
97
    function deployWithCreationCodeAndConstructorArgs(
98
        string memory _saltString,
99
        bytes memory creationCode,
100
        bytes memory constructionArgs
101
    ) external returns (address) {
102
        bytes memory _data = abi.encodePacked(creationCode, constructionArgs);
103
        return deploy(_saltString, _data);
104
    }
105

                            
                        
106
    function deployWithCreationCode(
107
        string memory _saltString,
108
        bytes memory creationCode
109
    ) external returns (address) {
110
        return deploy(_saltString, creationCode);
111
    }
112

                            
                        
113
    function addressOf(string memory _saltString) external view returns (address) {
114
        bytes32 _salt = keccak256(abi.encodePacked(_saltString));
115
        return Create3.addressOf(_salt);
116
    }
117

                            
                        
118
    function addressOfSalt(bytes32 _salt) external view returns (address) {
119
        return Create3.addressOf(_salt);
120
    }
121

                            
                        
122
    /**
123
        @notice Create the creation code for a contract with the given runtime code.
124
        @dev credit: https://github.com/0xsequence/create3/blob/master/contracts/test_utils/Create3Imp.sol
125
     */
126
    function creationCodeFor(bytes memory _code) internal pure returns (bytes memory) {
127
        /*
128
      0x00    0x63         0x63XXXXXX  PUSH4 _code.length  size
129
      0x01    0x80         0x80        DUP1                size size
130
      0x02    0x60         0x600e      PUSH1 14            14 size size
131
      0x03    0x60         0x6000      PUSH1 00            0 14 size size
132
      0x04    0x39         0x39        CODECOPY            size
133
      0x05    0x60         0x6000      PUSH1 00            0 size
134
      0x06    0xf3         0xf3        RETURN
135
      <CODE>
136
    */
137

                            
                        
138
        return
139
            abi.encodePacked(hex"63", uint32(_code.length), hex"80_60_0E_60_00_39_60_00_F3", _code);
140
    }
141
}
142

                            
                        

Lines covered: 41 / 104 (39.4%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IEBTCToken.sol";
6

                            
                        
7
import "./Dependencies/AuthNoOwner.sol";
8
import "./Dependencies/PermitNonce.sol";
9

                            
                        
10
/*
11
 *
12
 * Based upon OpenZeppelin's ERC20 contract:
13
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
14
 *
15
 * and their EIP2612 (ERC20Permit / ERC712) functionality:
16
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/53516bc555a454862470e7860a9b5254db4d00f5/contracts/token/ERC20/ERC20Permit.sol
17
 *
18
 *
19
 * --- Functionality added specific to the EBTCToken ---
20
 *
21
 * 1) Transfer protection: blacklist of addresses that are invalid recipients (i.e. core Liquity contracts) in external
22
 * transfer() and transferFrom() calls. The purpose is to protect users from losing tokens by mistakenly sending EBTC directly to a Liquity
23
 * core contract, when they should rather call the right function.
24
 */
25

                            
                        
26
contract EBTCToken is IEBTCToken, AuthNoOwner, PermitNonce {
27
    uint256 private _totalSupply;
28
    string internal constant _NAME = "EBTC Stablecoin";
29
    string internal constant _SYMBOL = "EBTC";
30
    string internal constant _VERSION = "1";
31
    uint8 internal constant _DECIMALS = 18;
32

                            
                        
33
    // --- Data for EIP2612 ---
34

                            
                        
35
    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
36
    bytes32 private constant _PERMIT_TYPEHASH =
37
        0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
38
    // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
39
    bytes32 private constant _TYPE_HASH =
40
        0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
41

                            
                        
42
    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
43
    // invalidate the cached domain separator if the chain id changes.
44
    bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
45
    uint256 private immutable _CACHED_CHAIN_ID;
46

                            
                        
47
    bytes32 private immutable _HASHED_NAME;
48
    bytes32 private immutable _HASHED_VERSION;
49

                            
                        
50
    // User data for EBTC token
51
    mapping(address => uint256) private _balances;
52
    mapping(address => mapping(address => uint256)) private _allowances;
53

                            
                        
54
    // --- Addresses ---
55
    address public immutable cdpManagerAddress;
56
    address public immutable borrowerOperationsAddress;
57

                            
                        
58
    constructor(
59
        address _cdpManagerAddress,
60
        address _borrowerOperationsAddress,
61
        address _authorityAddress
62
    ) {
63
        _initializeAuthority(_authorityAddress);
64

                            
                        
65
        cdpManagerAddress = _cdpManagerAddress;
66
        borrowerOperationsAddress = _borrowerOperationsAddress;
67

                            
                        
68
        bytes32 hashedName = keccak256(bytes(_NAME));
69
        bytes32 hashedVersion = keccak256(bytes(_VERSION));
70

                            
                        
71
        _HASHED_NAME = hashedName;
72
        _HASHED_VERSION = hashedVersion;
73
        _CACHED_CHAIN_ID = _chainID();
74
        _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, hashedName, hashedVersion);
75
    }
76

                            
                        
77
    // --- Functions for intra-Liquity calls ---
78

                            
                        
79
    /**
80
     * @notice Mint new tokens
81
     * @dev Internal system function - only callable by BorrowerOperations or CDPManager
82
     * @dev Governance can also expand the list of approved minters to enable other systems to mint tokens
83
     * @param _account The address to receive the newly minted tokens
84
     * @param _amount The amount of tokens to mint
85
     */
86
    function mint(address _account, uint256 _amount) external override {
87
        _requireCallerIsBOorCdpMOrAuth();
88
        _mint(_account, _amount);
89
    }
90

                            
                        
91
    /**
92
     * @notice Burn existing tokens
93
     * @dev Internal system function - only callable by BorrowerOperations or CDPManager
94
     * @dev Governance can also expand the list of approved burners to enable other systems to burn tokens
95
     * @param _account The address to burn tokens from
96
     * @param _amount The amount of tokens to burn
97
     */
98
    function burn(address _account, uint256 _amount) external override {
99
        _requireCallerIsBOorCdpMOrAuth();
100
        _burn(_account, _amount);
101
    }
102

                            
                        
103
    /**
104
     * @notice Burn existing tokens from caller
105
     * @dev Internal system function - only callable by BorrowerOperations or CDPManager
106
     * @dev Governance can also expand the list of approved burners to enable other systems to burn tokens
107
     * @param _amount The amount of tokens to burn
108
     */
109
    function burn(uint256 _amount) external {
110
        _requireCallerIsBOorCdpMOrAuth();
111
        _burn(msg.sender, _amount);
112
    }
113

                            
                        
114
    // --- External functions ---
115

                            
                        
116
    function totalSupply() external view override returns (uint256) {
117
        return _totalSupply;
118
    }
119

                            
                        
120
    function balanceOf(address account) external view override returns (uint256) {
121
        return _balances[account];
122
    }
123

                            
                        
124
    function transfer(address recipient, uint256 amount) external override returns (bool) {
125
        _requireValidRecipient(recipient);
126
        _transfer(msg.sender, recipient, amount);
127
        return true;
128
    }
129

                            
                        
130
    function allowance(address owner, address spender) external view override returns (uint256) {
131
        return _allowances[owner][spender];
132
    }
133

                            
                        
134
    function approve(address spender, uint256 amount) external override returns (bool) {
135
        _approve(msg.sender, spender, amount);
136
        return true;
137
    }
138

                            
                        
139
    function transferFrom(
140
        address sender,
141
        address recipient,
142
        uint256 amount
143
    ) external override returns (bool) {
144
        _requireValidRecipient(recipient);
145
        _transfer(sender, recipient, amount);
146

                            
                        
147
        uint256 cachedAllowance = _allowances[sender][msg.sender];
148
        if (cachedAllowance != type(uint256).max) {
149
            require(cachedAllowance >= amount, "ERC20: transfer amount exceeds allowance");
150
            unchecked {
151
                _approve(sender, msg.sender, cachedAllowance - amount);
152
            }
153
        }
154
        return true;
155
    }
156

                            
                        
157
    function increaseAllowance(
158
        address spender,
159
        uint256 addedValue
160
    ) external override returns (bool) {
161
        _approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);
162
        return true;
163
    }
164

                            
                        
165
    function decreaseAllowance(
166
        address spender,
167
        uint256 subtractedValue
168
    ) external override returns (bool) {
169
        uint256 cachedAllowances = _allowances[msg.sender][spender];
170
        require(cachedAllowances >= subtractedValue, "ERC20: decreased allowance below zero");
171
        unchecked {
172
            _approve(msg.sender, spender, cachedAllowances - subtractedValue);
173
        }
174
        return true;
175
    }
176

                            
                        
177
    // --- EIP 2612 Functionality (https://eips.ethereum.org/EIPS/eip-2612) ---
178

                            
                        
179
    function DOMAIN_SEPARATOR() external view returns (bytes32) {
180
        return domainSeparator();
181
    }
182

                            
                        
183
    function domainSeparator() public view override returns (bytes32) {
184
        if (_chainID() == _CACHED_CHAIN_ID) {
185
            return _CACHED_DOMAIN_SEPARATOR;
186
        } else {
187
            return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
188
        }
189
    }
190

                            
                        
191
    function permit(
192
        address owner,
193
        address spender,
194
        uint256 amount,
195
        uint256 deadline,
196
        uint8 v,
197
        bytes32 r,
198
        bytes32 s
199
    ) external override {
200
        require(deadline >= block.timestamp, "EBTC: expired deadline");
201
        bytes32 digest = keccak256(
202
            abi.encodePacked(
203
                "\x19\x01",
204
                domainSeparator(),
205
                keccak256(
206
                    abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner]++, deadline)
207
                )
208
            )
209
        );
210
        address recoveredAddress = ecrecover(digest, v, r, s);
211
        require(recoveredAddress == owner, "EBTC: invalid signature");
212
        _approve(owner, spender, amount);
213
    }
214

                            
                        
215
    /// @dev Return current nonce for msg.sender fOR EIP-2612 compatibility
216
    function nonces(address owner) external view override(IERC2612, PermitNonce) returns (uint256) {
217
        return _nonces[owner];
218
    }
219

                            
                        
220
    // --- Internal operations ---
221

                            
                        
222
    function _chainID() private view returns (uint256) {
223
        return block.chainid;
224
    }
225

                            
                        
226
    function _buildDomainSeparator(
227
        bytes32 typeHash,
228
        bytes32 name,
229
        bytes32 version
230
    ) private view returns (bytes32) {
231
        return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));
232
    }
233

                            
                        
234
    // --- Internal operations ---
235
    // Warning: sanity checks (for sender and recipient) should have been done before calling these internal functions
236

                            
                        
237
    function _transfer(address sender, address recipient, uint256 amount) internal {
238
        require(sender != address(0), "EBTCToken: zero sender!");
239
        require(recipient != address(0), "EBTCToken: zero recipient!");
240

                            
                        
241
        uint256 cachedSenderBalances = _balances[sender];
242
        require(cachedSenderBalances >= amount, "ERC20: transfer amount exceeds balance");
243

                            
                        
244
        unchecked {
245
            // Safe because of the check above
246
            _balances[sender] = cachedSenderBalances - amount;
247
        }
248

                            
                        
249
        _balances[recipient] = _balances[recipient] + amount;
250
        emit Transfer(sender, recipient, amount);
251
    }
252

                            
                        
253
    function _mint(address account, uint256 amount) internal {
254
        require(account != address(0), "EBTCToken: mint to zero recipient!");
255

                            
                        
256
        _totalSupply = _totalSupply + amount;
257
        _balances[account] = _balances[account] + amount;
258
        emit Transfer(address(0), account, amount);
259
    }
260

                            
                        
261
    function _burn(address account, uint256 amount) internal {
262
        require(account != address(0), "EBTCToken: burn from zero account!");
263

                            
                        
264
        uint256 cachedBalance = _balances[account];
265
        require(cachedBalance >= amount, "ERC20: burn amount exceeds balance");
266

                            
                        
267
        unchecked {
268
            // Safe because of the check above
269
            _balances[account] = cachedBalance - amount;
270
        }
271

                            
                        
272
        _totalSupply = _totalSupply - amount;
273
        emit Transfer(account, address(0), amount);
274
    }
275

                            
                        
276
    function _approve(address owner, address spender, uint256 amount) internal {
277
        require(owner != address(0), "EBTCToken: zero approve owner!");
278
        require(spender != address(0), "EBTCToken: zero approve spender!");
279

                            
                        
280
        _allowances[owner][spender] = amount;
281
        emit Approval(owner, spender, amount);
282
    }
283

                            
                        
284
    // --- 'require' functions ---
285

                            
                        
286
    function _requireValidRecipient(address _recipient) internal view {
287
        require(
288
            _recipient != address(0) && _recipient != address(this),
289
            "EBTC: Cannot transfer tokens directly to the EBTC token contract or the zero address"
290
        );
291
        require(
292
            _recipient != cdpManagerAddress && _recipient != borrowerOperationsAddress,
293
            "EBTC: Cannot transfer tokens directly to the CdpManager or BorrowerOps"
294
        );
295
    }
296

                            
                        
297
    function _requireCallerIsBorrowerOperations() internal view {
298
        require(
299
            msg.sender == borrowerOperationsAddress,
300
            "EBTCToken: Caller is not BorrowerOperations"
301
        );
302
    }
303

                            
                        
304
    /// @dev authority check last to short-circuit in the case of use by usual immutable addresses
305
    function _requireCallerIsBOorCdpMOrAuth() internal view {
306
        require(
307
            msg.sender == borrowerOperationsAddress ||
308
                msg.sender == cdpManagerAddress ||
309
                isAuthorized(msg.sender, msg.sig),
310
            "EBTC: Caller is neither BorrowerOperations nor CdpManager nor authorized"
311
        );
312
    }
313

                            
                        
314
    function _requireCallerIsCdpM() internal view {
315
        require(msg.sender == cdpManagerAddress, "EBTC: Caller is not CdpManager");
316
    }
317

                            
                        
318
    // --- Optional functions ---
319

                            
                        
320
    function name() external pure override returns (string memory) {
321
        return _NAME;
322
    }
323

                            
                        
324
    function symbol() external pure override returns (string memory) {
325
        return _SYMBOL;
326
    }
327

                            
                        
328
    function decimals() external pure override returns (uint8) {
329
        return _DECIMALS;
330
    }
331

                            
                        
332
    function version() external pure override returns (string memory) {
333
        return _VERSION;
334
    }
335

                            
                        
336
    function permitTypeHash() external pure override returns (bytes32) {
337
        return _PERMIT_TYPEHASH;
338
    }
339
}
340

                            
                        

Lines covered: 2 / 8 (25.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Dependencies/Ownable.sol";
6
import "./Dependencies/AuthNoOwner.sol";
7
import "./Dependencies/IERC20.sol";
8
import "./Dependencies/SafeERC20.sol";
9

                            
                        
10
/**
11
    @notice Minimal fee recipient
12
    @notice Tokens can be swept to owner address by authorized user
13
 */
14
contract FeeRecipient is Ownable, AuthNoOwner {
15
    using SafeERC20 for IERC20;
16
    // --- Events ---
17
    event SweepTokenSuccess(address indexed _token, uint256 _amount, address indexed _recipient);
18

                            
                        
19
    // --- Data ---
20
    string public constant NAME = "FeeRecipient";
21

                            
                        
22
    constructor(address _ownerAddress, address _authorityAddress) {
23
        _transferOwnership(_ownerAddress);
24
        _initializeAuthority(_authorityAddress);
25
    }
26

                            
                        
27
    // === Governed Functions === //
28

                            
                        
29
    /// @dev Function to move unintended dust that are not protected
30
    /// @notice moves given amount of given token (collateral is NOT allowed)
31
    /// @notice because recipient are fixed, this function is safe to be called by anyone
32
    function sweepToken(address token, uint256 amount) public requiresAuth {
33
        uint256 balance = IERC20(token).balanceOf(address(this));
34
        require(amount <= balance, "FeeRecipient: Attempt to sweep more than balance");
35

                            
                        
36
        address _owner = owner();
37
        IERC20(token).safeTransfer(_owner, amount);
38

                            
                        
39
        emit SweepTokenSuccess(token, amount, _owner);
40
    }
41
}
42

                            
                        

Lines covered: 4 / 66 (6.1%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import {EnumerableSet} from "./Dependencies/EnumerableSet.sol";
5
import {Authority} from "./Dependencies/Auth.sol";
6
import {RolesAuthority} from "./Dependencies/RolesAuthority.sol";
7

                            
                        
8
/// @notice Role based Authority that supports up to 256 roles.
9
/// @notice We have taken the tradeoff of additional storage usage for easier readabiliy without using off-chain / indexing services.
10
/// @author BadgerDAO Expanded from Solmate RolesAuthority
11
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol)
12
/// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol)
13
contract Governor is RolesAuthority {
14
    using EnumerableSet for EnumerableSet.Bytes32Set;
15
    using EnumerableSet for EnumerableSet.AddressSet;
16

                            
                        
17
    bytes32 NO_ROLES = bytes32(0);
18

                            
                        
19
    struct Role {
20
        uint8 roleId;
21
        string roleName;
22
    }
23

                            
                        
24
    struct Capability {
25
        address target;
26
        bytes4 functionSig;
27
        uint8[] roles;
28
    }
29

                            
                        
30
    /*//////////////////////////////////////////////////////////////
31
                            STORAGE
32
    //////////////////////////////////////////////////////////////*/
33

                            
                        
34
    mapping(uint8 => string) internal roleNames;
35

                            
                        
36
    /*//////////////////////////////////////////////////////////////
37
                                 EVENTS
38
    //////////////////////////////////////////////////////////////*/
39

                            
                        
40
    event RoleNameSet(uint8 indexed role, string indexed name);
41

                            
                        
42
    /*//////////////////////////////////////////////////////////////
43
                               CONSTRUCTOR
44
    //////////////////////////////////////////////////////////////*/
45

                            
                        
46
    /// @notice The contract constructor initializes RolesAuthority with the given owner.
47
    /// @param _owner The address of the owner, who gains all permissions by default.
48
    constructor(address _owner) RolesAuthority(_owner, Authority(address(this))) {}
49

                            
                        
50
    /*//////////////////////////////////////////////////////////////
51
                            GETTERS
52
    //////////////////////////////////////////////////////////////*/
53

                            
                        
54
    /// @notice Returns a list of users that are assigned a specific role.
55
    /// @dev This function searches all users and checks if they are assigned the given role.
56
    /// @dev Intended for off-chain utility only due to inefficiency.
57
    /// @param role The role ID to find users for.
58
    /// @return usersWithRole An array of addresses that are assigned the given role.
59

                            
                        
60
    function getUsersByRole(uint8 role) external view returns (address[] memory usersWithRole) {
61
        // Search over all users: O(n) * 2
62
        uint256 count;
63
        for (uint256 i = 0; i < users.length(); i++) {
64
            address user = users.at(i);
65
            bool _canCall = doesUserHaveRole(user, role);
66
            if (_canCall) {
67
                count += 1;
68
            }
69
        }
70
        if (count > 0) {
71
            uint256 j = 0;
72
            usersWithRole = new address[](count);
73
            address[] memory _usrs = users.values();
74
            for (uint256 i = 0; i < _usrs.length; i++) {
75
                address user = _usrs[i];
76
                bool _canCall = doesUserHaveRole(user, role);
77
                if (_canCall) {
78
                    usersWithRole[j] = user;
79
                    j++;
80
                }
81
            }
82
        }
83
    }
84

                            
                        
85
    /// @notice Returns a list of roles that an address has.
86
    /// @dev This function searches all roles and checks if they are assigned to the given user.
87
    /// @dev Intended for off-chain utility only due to inefficiency.
88
    /// @param user The address of the user.
89
    /// @return rolesForUser An array of role IDs that the user has.
90

                            
                        
91
    function getRolesForUser(address user) external view returns (uint8[] memory rolesForUser) {
92
        // Enumerate over all possible roles and check if enabled
93
        uint256 count;
94
        for (uint8 i = 0; i < type(uint8).max; i++) {
95
            if (doesUserHaveRole(user, i)) {
96
                count += 1;
97
            }
98
        }
99
        if (count > 0) {
100
            uint256 j = 0;
101
            rolesForUser = new uint8[](count);
102
            for (uint8 i = 0; i < type(uint8).max; i++) {
103
                if (doesUserHaveRole(user, i)) {
104
                    rolesForUser[j] = i;
105
                    j++;
106
                }
107
            }
108
        }
109
    }
110

                            
                        
111
    function getRolesFromByteMap(bytes32 byteMap) public pure returns (uint8[] memory roleIds) {
112
        uint256 count;
113
        for (uint8 i = 0; i < type(uint8).max; i++) {
114
            bool roleEnabled = (uint256(byteMap >> i) & 1) != 0;
115
            if (roleEnabled) {
116
                count += 1;
117
            }
118
        }
119
        if (count > 0) {
120
            uint256 j = 0;
121
            roleIds = new uint8[](count);
122
            for (uint8 i = 0; i < type(uint8).max; i++) {
123
                bool roleEnabled = (uint256(byteMap >> i) & 1) != 0;
124
                if (roleEnabled) {
125
                    roleIds[j] = i;
126
                    j++;
127
                }
128
            }
129
        }
130
    }
131

                            
                        
132
    // helper function to generate bytes32 cache data for given roleIds array
133
    function getByteMapFromRoles(uint8[] memory roleIds) public pure returns (bytes32) {
134
        bytes32 _data;
135
        for (uint8 i = 0; i < roleIds.length; i++) {
136
            _data |= bytes32(1 << uint256(roleIds[i]));
137
        }
138
        return _data;
139
    }
140

                            
                        
141
    // helper function to return every authorization-enabled function signatures for given target address
142
    function getEnabledFunctionsInTarget(
143
        address _target
144
    ) public view returns (bytes4[] memory _funcs) {
145
        bytes32[] memory _sigs = enabledFunctionSigsByTarget[_target].values();
146
        if (_sigs.length > 0) {
147
            _funcs = new bytes4[](_sigs.length);
148
            for (uint256 i = 0; i < _sigs.length; ++i) {
149
                _funcs[i] = bytes4(_sigs[i]);
150
            }
151
        }
152
    }
153

                            
                        
154
    /// @notice return all role IDs that have at least one capability enabled
155
    function getActiveRoles() external view returns (Role[] memory activeRoles) {
156
        revert("Planned off-chain QOL function, not yet implemented, please ignore for audit");
157
    }
158

                            
                        
159
    // If a role exists, flip enabled
160

                            
                        
161
    // Return all roles that are enabled anywhere
162

                            
                        
163
    function getCapabilitiesForTarget(
164
        address target
165
    ) external view returns (Capability[] memory capabilities) {
166
        revert("Planned off-chain QOL function, not yet implemented, please ignore for audit");
167
    }
168

                            
                        
169
    function getCapabilitiesByRole(
170
        uint8 role
171
    ) external view returns (Capability[] memory capabilities) {
172
        revert("Planned off-chain QOL function, not yet implemented, please ignore for audit");
173
    }
174

                            
                        
175
    function getRoleName(uint8 role) external view returns (string memory roleName) {
176
        return roleNames[role];
177
    }
178

                            
                        
179
    /*//////////////////////////////////////////////////////////////
180
                            AUTHORIZED SETTERS
181
    //////////////////////////////////////////////////////////////*/
182

                            
                        
183
    function setRoleName(uint8 role, string memory roleName) external requiresAuth {
184
        // TODO: require maximum size for a name
185
        roleNames[role] = roleName;
186

                            
                        
187
        emit RoleNameSet(role, roleName);
188
    }
189
}
190

                            
                        

Lines covered: 3 / 86 (3.5%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ICdpManager.sol";
6
import "./Interfaces/ISortedCdps.sol";
7
import "./Dependencies/LiquityBase.sol";
8

                            
                        
9
contract HintHelpers is LiquityBase {
10
    string public constant NAME = "HintHelpers";
11

                            
                        
12
    ISortedCdps public immutable sortedCdps;
13
    ICdpManager public immutable cdpManager;
14

                            
                        
15
    // --- Events ---
16

                            
                        
17
    struct LocalVariables_getRedemptionHints {
18
        uint256 remainingEbtcToRedeem;
19
        uint256 minNetDebtInBTC;
20
        bytes32 currentCdpId;
21
        address currentCdpUser;
22
    }
23

                            
                        
24
    // --- Dependency setters ---
25
    constructor(
26
        address _sortedCdpsAddress,
27
        address _cdpManagerAddress,
28
        address _collateralAddress,
29
        address _activePoolAddress,
30
        address _priceFeedAddress
31
    ) LiquityBase(_activePoolAddress, _priceFeedAddress, _collateralAddress) {
32
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
33
        cdpManager = ICdpManager(_cdpManagerAddress);
34
    }
35

                            
                        
36
    // --- Functions ---
37

                            
                        
38
    /**
39
     * @notice Get the redemption hints for the specified amount of eBTC, price and maximum number of iterations.
40
     * @param _EBTCamount The amount of eBTC to be redeemed.
41
     * @param _price The current price of the asset.
42
     * @param _maxIterations The maximum number of iterations to be performed.
43
     * @return firstRedemptionHint The identifier of the first CDP to be considered for redemption.
44
     * @return partialRedemptionHintNICR The new Nominal Collateral Ratio (NICR) of the CDP after partial redemption.
45
     * @return truncatedEBTCamount The actual amount of eBTC that can be redeemed.
46
     * @return partialRedemptionNewColl The new collateral amount after partial redemption.
47
     */
48
    function getRedemptionHints(
49
        uint256 _EBTCamount,
50
        uint256 _price,
51
        uint256 _maxIterations
52
    )
53
        external
54
        view
55
        returns (
56
            bytes32 firstRedemptionHint,
57
            uint256 partialRedemptionHintNICR,
58
            uint256 truncatedEBTCamount,
59
            uint256 partialRedemptionNewColl
60
        )
61
    {
62
        LocalVariables_getRedemptionHints memory vars;
63
        {
64
            vars.remainingEbtcToRedeem = _EBTCamount;
65
            vars.currentCdpId = sortedCdps.getLast();
66
            vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);
67

                            
                        
68
            while (
69
                vars.currentCdpUser != address(0) &&
70
                cdpManager.getICR(vars.currentCdpId, _price) < MCR
71
            ) {
72
                vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);
73
                vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);
74
            }
75
            firstRedemptionHint = vars.currentCdpId;
76
        }
77

                            
                        
78
        if (_maxIterations == 0) {
79
            _maxIterations = type(uint256).max;
80
        }
81

                            
                        
82
        // Underflow is intentionally used in _maxIterations-- > 0
83
        unchecked {
84
            while (
85
                vars.currentCdpUser != address(0) &&
86
                vars.remainingEbtcToRedeem > 0 &&
87
                _maxIterations-- > 0
88
            ) {
89
                // Apply pending debt
90
                uint256 currentCdpDebt = cdpManager.getCdpDebt(vars.currentCdpId) +
91
                    cdpManager.getPendingRedistributedDebt(vars.currentCdpId);
92

                            
                        
93
                // If this CDP has more debt than the remaining to redeem, attempt a partial redemption
94
                if (currentCdpDebt > vars.remainingEbtcToRedeem) {
95
                    uint256 _cachedEbtcToRedeem = vars.remainingEbtcToRedeem;
96
                    (
97
                        partialRedemptionNewColl,
98
                        partialRedemptionHintNICR
99
                    ) = _calculateCdpStateAfterPartialRedemption(vars, currentCdpDebt, _price);
100

                            
                        
101
                    // If the partial redemption would leave the CDP with less than the minimum allowed coll, bail out of partial redemption and return only the fully redeemable
102
                    // TODO: This seems to return the original coll? why?
103
                    if (collateral.getPooledEthByShares(partialRedemptionNewColl) < MIN_NET_COLL) {
104
                        partialRedemptionHintNICR = 0; //reset to 0 as there is no partial redemption in this case
105
                        partialRedemptionNewColl = 0;
106
                        vars.remainingEbtcToRedeem = _cachedEbtcToRedeem;
107
                    } else {
108
                        vars.remainingEbtcToRedeem = 0;
109
                    }
110
                    break;
111
                } else {
112
                    vars.remainingEbtcToRedeem = vars.remainingEbtcToRedeem - currentCdpDebt;
113
                }
114

                            
                        
115
                vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);
116
                vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);
117
            }
118
        }
119

                            
                        
120
        truncatedEBTCamount = _EBTCamount - vars.remainingEbtcToRedeem;
121
    }
122

                            
                        
123
    /**
124
     * @notice Calculate the partial redemption information.
125
     * @dev This is an internal function used by getRedemptionHints.
126
     * @param vars The local variables of the getRedemptionHints function.
127
     * @param currentCdpDebt The net eBTC debt of the CDP.
128
     * @param _price The current price of the asset.
129
     * @return newColl The new collateral amount after partial redemption.
130
     * @return newNICR The new Nominal Collateral Ratio (NICR) of the CDP after partial redemption.
131
     */
132
    function _calculateCdpStateAfterPartialRedemption(
133
        LocalVariables_getRedemptionHints memory vars,
134
        uint256 currentCdpDebt,
135
        uint256 _price
136
    ) internal view returns (uint256, uint256) {
137
        // maxReemable = min(remainingToRedeem, currentDebt)
138
        uint256 maxRedeemableEBTC = LiquityMath._min(vars.remainingEbtcToRedeem, currentCdpDebt);
139

                            
                        
140
        uint256 newColl;
141
        uint256 _oldIndex = cdpManager.stEthIndex();
142
        uint256 _newIndex = collateral.getPooledEthByShares(DECIMAL_PRECISION);
143

                            
                        
144
        if (_oldIndex < _newIndex) {
145
            newColl = _getCollateralWithSplitFeeApplied(vars.currentCdpId, _newIndex, _oldIndex);
146
        } else {
147
            (, newColl, ) = cdpManager.getDebtAndCollShares(vars.currentCdpId);
148
        }
149

                            
                        
150
        vars.remainingEbtcToRedeem = vars.remainingEbtcToRedeem - maxRedeemableEBTC;
151
        uint256 collToReceive = collateral.getSharesByPooledEth(
152
            (maxRedeemableEBTC * DECIMAL_PRECISION) / _price
153
        );
154

                            
                        
155
        uint256 _newCollAfter = newColl - collToReceive;
156
        return (
157
            _newCollAfter,
158
            LiquityMath._computeNominalCR(_newCollAfter, currentCdpDebt - maxRedeemableEBTC)
159
        );
160
    }
161

                            
                        
162
    /**
163
     * @notice Get the collateral amount of a CDP after applying split fee.
164
     * @dev This is an internal function used by _calculateCdpStateAfterPartialRedemption.
165
     * @param _cdpId The identifier of the CDP.
166
     * @param _newIndex The new index after the split fee is applied.
167
     * @param _oldIndex The old index before the split fee is applied.
168
     * @return newColl The new collateral amount after applying split fee.
169
     */
170
    function _getCollateralWithSplitFeeApplied(
171
        bytes32 _cdpId,
172
        uint256 _newIndex,
173
        uint256 _oldIndex
174
    ) internal view returns (uint256) {
175
        uint256 _deltaFeePerUnit;
176
        uint256 _newStFeePerUnit;
177
        uint256 _perUnitError;
178
        uint256 _feeTaken;
179

                            
                        
180
        (_feeTaken, _deltaFeePerUnit, _perUnitError) = cdpManager.calcFeeUponStakingReward(
181
            _newIndex,
182
            _oldIndex
183
        );
184
        _newStFeePerUnit = _deltaFeePerUnit + cdpManager.systemStEthFeePerUnitIndex();
185
        (, uint256 newColl) = cdpManager.getAccumulatedFeeSplitApplied(_cdpId, _newStFeePerUnit);
186
        return newColl;
187
    }
188

                            
                        
189
    /* getApproxHint() - return address of a Cdp that is, on average, (length / numTrials) positions away in the 
190
    sortedCdps list from the correct insert position of the Cdp to be inserted. 
191
    
192
    Note: The output address is worst-case O(n) positions away from the correct insert position, however, the function 
193
    is probabilistic. Input can be tuned to guarantee results to a high degree of confidence, e.g:
194

                            
                        
195
    Submitting numTrials = k * sqrt(length), with k = 15 makes it very, very likely that the ouput address will 
196
    be <= sqrt(length) positions away from the correct insert position.
197
    */
198
    function getApproxHint(
199
        uint256 _CR,
200
        uint256 _numTrials,
201
        uint256 _inputRandomSeed
202
    ) external view returns (bytes32 hint, uint256 diff, uint256 latestRandomSeed) {
203
        uint256 arrayLength = cdpManager.getActiveCdpsCount();
204

                            
                        
205
        if (arrayLength == 0) {
206
            return (sortedCdps.nonExistId(), 0, _inputRandomSeed);
207
        }
208

                            
                        
209
        hint = sortedCdps.getLast();
210
        diff = LiquityMath._getAbsoluteDifference(_CR, cdpManager.getNominalICR(hint));
211
        latestRandomSeed = _inputRandomSeed;
212

                            
                        
213
        uint256 i = 1;
214

                            
                        
215
        while (i < _numTrials) {
216
            latestRandomSeed = uint256(keccak256(abi.encodePacked(latestRandomSeed)));
217

                            
                        
218
            uint256 arrayIndex = latestRandomSeed % arrayLength;
219
            bytes32 _cId = cdpManager.getIdFromCdpIdsArray(arrayIndex);
220
            uint256 currentNICR = cdpManager.getNominalICR(_cId);
221

                            
                        
222
            // check if abs(current - CR) > abs(closest - CR), and update closest if current is closer
223
            uint256 currentDiff = LiquityMath._getAbsoluteDifference(currentNICR, _CR);
224

                            
                        
225
            if (currentDiff < diff) {
226
                diff = currentDiff;
227
                hint = _cId;
228
            }
229
            i++;
230
        }
231
    }
232

                            
                        
233
    /// @notice Compute nominal CR for a specified collateral and debt amount
234
    function computeNominalCR(uint256 _coll, uint256 _debt) external pure returns (uint256) {
235
        return LiquityMath._computeNominalCR(_coll, _debt);
236
    }
237

                            
                        
238
    /// @notice Compute CR for a specified collateral and debt amount
239
    function computeCR(
240
        uint256 _coll,
241
        uint256 _debt,
242
        uint256 _price
243
    ) external pure returns (uint256) {
244
        return LiquityMath._computeCR(_coll, _debt, _price);
245
    }
246
}
247

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./IPool.sol";
6

                            
                        
7
interface IActivePool is IPool {
8
    // --- Events ---
9
    event ActivePoolEBTCDebtUpdated(uint256 _EBTCDebt);
10
    event SystemCollSharesUpdated(uint256 _coll);
11
    event FeeRecipientAddressChanged(address _feeRecipientAddress);
12
    event FeeRecipientClaimableCollSharesIncreased(uint256 _coll, uint256 _fee);
13
    event FeeRecipientClaimableCollSharesDecreased(uint256 _coll, uint256 _fee);
14
    event FlashLoanSuccess(address _receiver, address _token, uint256 _amount, uint256 _fee);
15
    event SweepTokenSuccess(address indexed _token, uint256 _amount, address indexed _recipient);
16

                            
                        
17
    // --- Functions ---
18
    function transferSystemCollShares(address _account, uint256 _amount) external;
19

                            
                        
20
    function increaseSystemCollShares(uint256 _value) external;
21

                            
                        
22
    function transferSystemCollSharesAndLiquidatorReward(
23
        address _account,
24
        uint256 _shares,
25
        uint256 _liquidatorRewardShares
26
    ) external;
27

                            
                        
28
    function allocateSystemCollSharesToFeeRecipient(uint256 _shares) external;
29

                            
                        
30
    function claimFeeRecipientCollShares(uint256 _shares) external;
31

                            
                        
32
    function feeRecipientAddress() external view returns (address);
33

                            
                        
34
    function getFeeRecipientClaimableCollShares() external view returns (uint256);
35

                            
                        
36
    function setFeeRecipientAddress(address _feeRecipientAddress) external;
37
}
38

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4
import "./IPositionManagers.sol";
5

                            
                        
6
// Common interface for the Cdp Manager.
7
interface IBorrowerOperations is IPositionManagers {
8
    // --- Events ---
9

                            
                        
10
    event FeeRecipientAddressChanged(address _feeRecipientAddress);
11
    event FlashLoanSuccess(address _receiver, address _token, uint256 _amount, uint256 _fee);
12

                            
                        
13
    // --- Functions ---
14

                            
                        
15
    function openCdp(
16
        uint256 _EBTCAmount,
17
        bytes32 _upperHint,
18
        bytes32 _lowerHint,
19
        uint256 _stEthBalance
20
    ) external returns (bytes32);
21

                            
                        
22
    function openCdpFor(
23
        uint _EBTCAmount,
24
        bytes32 _upperHint,
25
        bytes32 _lowerHint,
26
        uint _collAmount,
27
        address _borrower
28
    ) external returns (bytes32);
29

                            
                        
30
    function addColl(
31
        bytes32 _cdpId,
32
        bytes32 _upperHint,
33
        bytes32 _lowerHint,
34
        uint256 _stEthBalanceIncrease
35
    ) external;
36

                            
                        
37
    function withdrawColl(
38
        bytes32 _cdpId,
39
        uint256 _stEthBalanceDecrease,
40
        bytes32 _upperHint,
41
        bytes32 _lowerHint
42
    ) external;
43

                            
                        
44
    function withdrawEBTC(
45
        bytes32 _cdpId,
46
        uint256 _amount,
47
        bytes32 _upperHint,
48
        bytes32 _lowerHint
49
    ) external;
50

                            
                        
51
    function repayEBTC(
52
        bytes32 _cdpId,
53
        uint256 _amount,
54
        bytes32 _upperHint,
55
        bytes32 _lowerHint
56
    ) external;
57

                            
                        
58
    function closeCdp(bytes32 _cdpId) external;
59

                            
                        
60
    function adjustCdp(
61
        bytes32 _cdpId,
62
        uint256 _stEthBalanceDecrease,
63
        uint256 _debtChange,
64
        bool isDebtIncrease,
65
        bytes32 _upperHint,
66
        bytes32 _lowerHint
67
    ) external;
68

                            
                        
69
    function adjustCdpWithColl(
70
        bytes32 _cdpId,
71
        uint256 _stEthBalanceDecrease,
72
        uint256 _debtChange,
73
        bool isDebtIncrease,
74
        bytes32 _upperHint,
75
        bytes32 _lowerHint,
76
        uint256 _stEthBalanceIncrease
77
    ) external;
78

                            
                        
79
    function claimSurplusCollShares() external;
80

                            
                        
81
    function feeRecipientAddress() external view returns (address);
82
}
83

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./ILiquityBase.sol";
6
import "./ICdpManagerData.sol";
7

                            
                        
8
// Common interface for the Cdp Manager.
9
interface ICdpManager is ILiquityBase, ICdpManagerData {
10
    // --- Functions ---
11
    function getActiveCdpsCount() external view returns (uint256);
12

                            
                        
13
    function getIdFromCdpIdsArray(uint256 _index) external view returns (bytes32);
14

                            
                        
15
    function liquidate(bytes32 _cdpId) external;
16

                            
                        
17
    function partiallyLiquidate(
18
        bytes32 _cdpId,
19
        uint256 _partialAmount,
20
        bytes32 _upperPartialHint,
21
        bytes32 _lowerPartialHint
22
    ) external;
23

                            
                        
24
    function batchLiquidateCdps(bytes32[] calldata _cdpArray) external;
25

                            
                        
26
    function redeemCollateral(
27
        uint256 _EBTCAmount,
28
        bytes32 _firstRedemptionHint,
29
        bytes32 _upperPartialRedemptionHint,
30
        bytes32 _lowerPartialRedemptionHint,
31
        uint256 _partialRedemptionHintNICR,
32
        uint256 _maxIterations,
33
        uint256 _maxFee
34
    ) external;
35

                            
                        
36
    function updateStakeAndTotalStakes(bytes32 _cdpId) external returns (uint256);
37

                            
                        
38
    function syncAccounting(bytes32 _cdpId) external;
39

                            
                        
40
    function getTotalStakeForFeeTaken(uint256 _feeTaken) external view returns (uint256, uint256);
41

                            
                        
42
    function closeCdp(bytes32 _cdpId, address _borrower, uint256 _debt, uint256 _coll) external;
43

                            
                        
44
    function removeStake(bytes32 _cdpId) external;
45

                            
                        
46
    function getRedemptionRate() external view returns (uint256);
47

                            
                        
48
    function getRedemptionRateWithDecay() external view returns (uint256);
49

                            
                        
50
    function getRedemptionFeeWithDecay(uint256 _ETHDrawn) external view returns (uint256);
51

                            
                        
52
    function decayBaseRateFromBorrowing() external;
53

                            
                        
54
    function getCdpStatus(bytes32 _cdpId) external view returns (uint256);
55

                            
                        
56
    function getCdpStake(bytes32 _cdpId) external view returns (uint256);
57

                            
                        
58
    function getCdpDebt(bytes32 _cdpId) external view returns (uint256);
59

                            
                        
60
    function getCdpCollShares(bytes32 _cdpId) external view returns (uint256);
61

                            
                        
62
    function getCdpLiquidatorRewardShares(bytes32 _cdpId) external view returns (uint);
63

                            
                        
64
    function initializeCdp(
65
        bytes32 _cdpId,
66
        uint256 _debt,
67
        uint256 _coll,
68
        uint256 _liquidatorRewardShares,
69
        address _borrower
70
    ) external;
71

                            
                        
72
    function updateCdp(
73
        bytes32 _cdpId,
74
        address _borrower,
75
        uint256 _coll,
76
        uint256 _debt,
77
        uint256 _newColl,
78
        uint256 _newDebt
79
    ) external;
80

                            
                        
81
    function getTCR(uint256 _price) external view returns (uint256);
82

                            
                        
83
    function checkRecoveryMode(uint256 _price) external view returns (bool);
84
}
85

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./ICollSurplusPool.sol";
6
import "./IEBTCToken.sol";
7
import "./ISortedCdps.sol";
8
import "./IActivePool.sol";
9
import "./IRecoveryModeGracePeriod.sol";
10
import "../Dependencies/ICollateralTokenOracle.sol";
11

                            
                        
12
// Common interface for the Cdp Manager.
13
interface ICdpManagerData is IRecoveryModeGracePeriod {
14
    // --- Events ---
15

                            
                        
16
    event FeeRecipientAddressChanged(address _feeRecipientAddress);
17
    event StakingRewardSplitSet(uint256 _stakingRewardSplit);
18
    event RedemptionFeeFloorSet(uint256 _redemptionFeeFloor);
19
    event MinuteDecayFactorSet(uint256 _minuteDecayFactor);
20
    event BetaSet(uint256 _beta);
21
    event RedemptionsPaused(bool _paused);
22

                            
                        
23
    event Liquidation(uint256 _liquidatedDebt, uint256 _liquidatedColl, uint256 _liqReward);
24
    event Redemption(
25
        uint256 _attemptedEBTCAmount,
26
        uint256 _actualEBTCAmount,
27
        uint256 _ETHSent,
28
        uint256 _ETHFee,
29
        address _redeemer
30
    );
31
    event CdpUpdated(
32
        bytes32 indexed _cdpId,
33
        address indexed _borrower,
34
        uint256 _oldDebt,
35
        uint256 _oldColl,
36
        uint256 _debt,
37
        uint256 _coll,
38
        uint256 _stake,
39
        CdpOperation _operation
40
    );
41
    event CdpLiquidated(
42
        bytes32 indexed _cdpId,
43
        address indexed _borrower,
44
        uint _debt,
45
        uint _coll,
46
        CdpOperation _operation,
47
        address _liquidator,
48
        uint _premiumToLiquidator
49
    );
50
    event CdpPartiallyLiquidated(
51
        bytes32 indexed _cdpId,
52
        address indexed _borrower,
53
        uint256 _debt,
54
        uint256 _coll,
55
        CdpOperation operation,
56
        address _liquidator,
57
        uint _premiumToLiquidator
58
    );
59
    event BaseRateUpdated(uint256 _baseRate);
60
    event LastRedemptionTimestampUpdated(uint256 _lastFeeOpTime);
61
    event TotalStakesUpdated(uint256 _newTotalStakes);
62
    event SystemSnapshotsUpdated(uint256 _totalStakesSnapshot, uint256 _totalCollateralSnapshot);
63
    event SystemDebtRedistributionIndexUpdated(uint256 _systemDebtRedistributionIndex);
64
    event CdpDebtRedistributionIndexUpdated(bytes32 _cdpId, uint256 _debtRedistributionIndex);
65
    event CdpArrayIndexUpdated(bytes32 _cdpId, uint256 _newIndex);
66
    event StEthIndexUpdated(uint256 _oldIndex, uint256 _newIndex, uint256 _updTimestamp);
67
    event CollateralFeePerUnitUpdated(uint256 _oldPerUnit, uint256 _newPerUnit, uint256 _feeTaken);
68
    event CdpFeeSplitApplied(
69
        bytes32 _cdpId,
70
        uint256 _oldPerUnitCdp,
71
        uint256 _newPerUnitCdp,
72
        uint256 _collReduced,
73
        uint256 collLeft
74
    );
75

                            
                        
76
    enum CdpOperation {
77
        openCdp,
78
        closeCdp,
79
        adjustCdp,
80
        syncAccounting,
81
        liquidateInNormalMode,
82
        liquidateInRecoveryMode,
83
        redeemCollateral,
84
        partiallyLiquidate
85
    }
86

                            
                        
87
    enum Status {
88
        nonExistent,
89
        active,
90
        closedByOwner,
91
        closedByLiquidation,
92
        closedByRedemption
93
    }
94

                            
                        
95
    // Store the necessary data for a cdp
96
    struct Cdp {
97
        uint256 debt;
98
        uint256 coll;
99
        uint256 stake;
100
        uint256 liquidatorRewardShares;
101
        Status status;
102
        uint128 arrayIndex;
103
    }
104

                            
                        
105
    /*
106
     * --- Variable container structs for liquidations ---
107
     *
108
     * These structs are used to hold, return and assign variables inside the liquidation functions,
109
     * in order to avoid the error: "CompilerError: Stack too deep".
110
     **/
111

                            
                        
112
    struct CdpDebtAndCollShares {
113
        uint256 entireDebt;
114
        uint256 entireColl;
115
        uint256 pendingDebtReward;
116
    }
117

                            
                        
118
    struct LiquidationLocals {
119
        bytes32 cdpId;
120
        uint256 partialAmount; // used only for partial liquidation, default 0 means full liquidation
121
        uint256 price;
122
        uint256 ICR;
123
        bytes32 upperPartialHint;
124
        bytes32 lowerPartialHint;
125
        bool recoveryModeAtStart;
126
        uint256 TCR;
127
        uint256 totalColSurplus;
128
        uint256 totalColToSend;
129
        uint256 totalDebtToBurn;
130
        uint256 totalDebtToRedistribute;
131
        uint256 totalColReward;
132
        bool sequenceLiq;
133
    }
134

                            
                        
135
    struct LiquidationRecoveryModeLocals {
136
        uint256 entireSystemDebt;
137
        uint256 entireSystemColl;
138
        uint256 totalDebtToBurn;
139
        uint256 totalColToSend;
140
        uint256 totalColSurplus;
141
        bytes32 cdpId;
142
        uint256 price;
143
        uint256 ICR;
144
        uint256 totalDebtToRedistribute;
145
        uint256 totalColReward;
146
        bool sequenceLiq;
147
    }
148

                            
                        
149
    struct LocalVariables_OuterLiquidationFunction {
150
        uint256 price;
151
        bool recoveryModeAtStart;
152
        uint256 liquidatedDebt;
153
        uint256 liquidatedColl;
154
    }
155

                            
                        
156
    struct LocalVariables_LiquidationSequence {
157
        uint256 i;
158
        uint256 ICR;
159
        bytes32 cdpId;
160
        bool backToNormalMode;
161
        uint256 entireSystemDebt;
162
        uint256 entireSystemColl;
163
        uint256 price;
164
        uint256 TCR;
165
    }
166

                            
                        
167
    struct SingleRedemptionInputs {
168
        bytes32 cdpId;
169
        uint256 maxEBTCamount;
170
        uint256 price;
171
        bytes32 upperPartialRedemptionHint;
172
        bytes32 lowerPartialRedemptionHint;
173
        uint256 partialRedemptionHintNICR;
174
    }
175

                            
                        
176
    struct LiquidationValues {
177
        uint256 entireCdpDebt;
178
        uint256 debtToOffset;
179
        uint256 totalCollToSendToLiquidator;
180
        uint256 debtToRedistribute;
181
        uint256 collSurplus;
182
        uint256 collReward;
183
    }
184

                            
                        
185
    struct LiquidationTotals {
186
        uint256 totalDebtInSequence;
187
        uint256 totalDebtToOffset;
188
        uint256 totalCollToSendToLiquidator;
189
        uint256 totalDebtToRedistribute;
190
        uint256 totalCollSurplus;
191
        uint256 totalCollReward;
192
    }
193

                            
                        
194
    // --- Variable container structs for redemptions ---
195

                            
                        
196
    struct RedemptionTotals {
197
        uint256 remainingEBTC;
198
        uint256 totalEBTCToRedeem;
199
        uint256 totalETHDrawn;
200
        uint256 totalCollSharesSurplus;
201
        uint256 ETHFee;
202
        uint256 ETHToSendToRedeemer;
203
        uint256 decayedBaseRate;
204
        uint256 price;
205
        uint256 totalEBTCSupplyAtStart;
206
        uint256 totalCollSharesAtStart;
207
        uint256 tcrAtStart;
208
    }
209

                            
                        
210
    struct SingleRedemptionValues {
211
        uint256 eBtcToRedeem;
212
        uint256 stEthToRecieve;
213
        uint256 collSurplus;
214
        uint256 liquidatorRewardShares;
215
        bool cancelledPartial;
216
        bool fullRedemption;
217
    }
218

                            
                        
219
    function totalStakes() external view returns (uint256);
220

                            
                        
221
    function ebtcToken() external view returns (IEBTCToken);
222

                            
                        
223
    function systemStEthFeePerUnitIndex() external view returns (uint256);
224

                            
                        
225
    function systemStEthFeePerUnitIndexError() external view returns (uint256);
226

                            
                        
227
    function stEthIndex() external view returns (uint256);
228

                            
                        
229
    function calcFeeUponStakingReward(
230
        uint256 _newIndex,
231
        uint256 _prevIndex
232
    ) external view returns (uint256, uint256, uint256);
233

                            
                        
234
    function syncGlobalAccounting() external; // Accrues StEthFeeSplit without influencing Grace Period
235

                            
                        
236
    function syncGlobalAccountingAndGracePeriod() external; // Accrues StEthFeeSplit and influences Grace Period
237

                            
                        
238
    function getAccumulatedFeeSplitApplied(
239
        bytes32 _cdpId,
240
        uint256 _systemStEthFeePerUnitIndex
241
    ) external view returns (uint256, uint256);
242

                            
                        
243
    function getNominalICR(bytes32 _cdpId) external view returns (uint256);
244

                            
                        
245
    function getICR(bytes32 _cdpId, uint256 _price) external view returns (uint256);
246

                            
                        
247
    function getSyncedCdpDebt(bytes32 _cdpId) external view returns (uint256);
248

                            
                        
249
    function getSyncedCdpCollShares(bytes32 _cdpId) external view returns (uint256);
250

                            
                        
251
    function getSyncedICR(bytes32 _cdpId, uint256 _price) external view returns (uint256);
252

                            
                        
253
    function getSyncedTCR(uint256 _price) external view returns (uint256);
254

                            
                        
255
    function getPendingRedistributedDebt(bytes32 _cdpId) external view returns (uint256);
256

                            
                        
257
    function hasPendingRedistributedDebt(bytes32 _cdpId) external view returns (bool);
258

                            
                        
259
    function getDebtAndCollShares(
260
        bytes32 _cdpId
261
    ) external view returns (uint256 debt, uint256 coll, uint256 pendingEBTCDebtReward);
262

                            
                        
263
    function canLiquidateRecoveryMode(uint256 icr, uint256 tcr) external view returns (bool);
264
}
265

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface ICollSurplusPool {
6
    // --- Events ---
7

                            
                        
8
    event SurplusCollSharesUpdated(address indexed _account, uint256 _newBalance);
9
    event CollSharesTransferred(address _to, uint256 _amount);
10

                            
                        
11
    event SweepTokenSuccess(address indexed _token, uint256 _amount, address indexed _recipient);
12

                            
                        
13
    // --- Contract setters ---
14

                            
                        
15
    function getTotalSurplusCollShares() external view returns (uint256);
16

                            
                        
17
    function getSurplusCollShares(address _account) external view returns (uint256);
18

                            
                        
19
    function increaseSurplusCollShares(address _account, uint256 _amount) external;
20

                            
                        
21
    function claimSurplusCollShares(address _account) external;
22

                            
                        
23
    function increaseTotalSurplusCollShares(uint256 _value) external;
24
}
25

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../Dependencies/IERC20.sol";
6
import "../Dependencies/IERC2612.sol";
7

                            
                        
8
interface IEBTCToken is IERC20, IERC2612 {
9
    // --- Events ---
10
    event EBTCTokenBalanceUpdated(address _user, uint _amount);
11

                            
                        
12
    // --- Functions ---
13

                            
                        
14
    function mint(address _account, uint256 _amount) external;
15

                            
                        
16
    function burn(address _account, uint256 _amount) external;
17
}
18

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IERC3156FlashBorrower {
6
    /**
7
     * @dev Receive a flash loan.
8
     * @param initiator The initiator of the loan.
9
     * @param token The loan currency.
10
     * @param amount The amount of tokens lent.
11
     * @param fee The additional amount of tokens to repay.
12
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
13
     * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
14
     */
15
    function onFlashLoan(
16
        address initiator,
17
        address token,
18
        uint256 amount,
19
        uint256 fee,
20
        bytes calldata data
21
    ) external returns (bytes32);
22
}
23

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./IERC3156FlashBorrower.sol";
6

                            
                        
7
interface IERC3156FlashLender {
8
    event FlashFeeSet(address _setter, uint256 _oldFee, uint256 _newFee);
9
    event MaxFlashFeeSet(address _setter, uint256 _oldMaxFee, uint256 _newMaxFee);
10
    event FlashLoansPaused(address _setter, bool _paused);
11

                            
                        
12
    /**
13
     * @dev The amount of currency available to be lent.
14
     * @param token The loan currency.
15
     * @return The amount of `token` that can be borrowed.
16
     */
17
    function maxFlashLoan(address token) external view returns (uint256);
18

                            
                        
19
    /**
20
     * @dev The fee to be charged for a given loan.
21
     * @param token The loan currency.
22
     * @param amount The amount of tokens lent.
23
     * @return The amount of `token` to be charged for the loan, on top of the returned principal.
24
     */
25
    function flashFee(address token, uint256 amount) external view returns (uint256);
26

                            
                        
27
    /**
28
     * @dev Initiate a flash loan.
29
     * @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
30
     * @param token The loan currency.
31
     * @param amount The amount of tokens lent.
32
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
33
     */
34
    function flashLoan(
35
        IERC3156FlashBorrower receiver,
36
        address token,
37
        uint256 amount,
38
        bytes calldata data
39
    ) external returns (bool);
40
}
41

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IFallbackCaller {
6
    // --- Events ---
7
    event FallbackTimeOutChanged(uint256 _oldTimeOut, uint256 _newTimeOut);
8

                            
                        
9
    // --- Function External View ---
10

                            
                        
11
    // NOTE: The fallback oracle must always return its answer scaled to 18 decimals where applicable
12
    //       The system will assume an 18 decimal response for efficiency.
13
    function getFallbackResponse() external view returns (uint256, uint256, bool);
14

                            
                        
15
    // NOTE: this returns the timeout window interval for the fallback oracle instead
16
    // of storing in the `PriceFeed` contract is retrieve for the `FallbackCaller`
17
    function fallbackTimeout() external view returns (uint256);
18

                            
                        
19
    // --- Function External Setter ---
20

                            
                        
21
    function setFallbackTimeout(uint256 newFallbackTimeout) external;
22
}
23

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./IPriceFeed.sol";
6

                            
                        
7
interface ILiquityBase {
8
    function priceFeed() external view returns (IPriceFeed);
9
}
10

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IPermitNonce {
6
    // --- Functions ---
7
    function increasePermitNonce() external returns (uint256);
8

                            
                        
9
    function nonces(address owner) external view returns (uint256);
10
}
11

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
// Common interface for the Pools.
6
interface IPool {
7
    // --- Events ---
8

                            
                        
9
    event ETHBalanceUpdated(uint256 _newBalance);
10
    event EBTCBalanceUpdated(uint256 _newBalance);
11
    event CollSharesTransferred(address _to, uint256 _amount);
12

                            
                        
13
    // --- Functions ---
14

                            
                        
15
    function getSystemCollShares() external view returns (uint256);
16

                            
                        
17
    function getSystemDebt() external view returns (uint256);
18

                            
                        
19
    function increaseSystemDebt(uint256 _amount) external;
20

                            
                        
21
    function decreaseSystemDebt(uint256 _amount) external;
22
}
23

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IPositionManagers {
6
    enum PositionManagerApproval {
7
        None,
8
        OneTime,
9
        Persistent
10
    }
11

                            
                        
12
    event PositionManagerApprovalSet(
13
        address _borrower,
14
        address _positionManager,
15
        PositionManagerApproval _approval
16
    );
17

                            
                        
18
    function getPositionManagerApproval(
19
        address _borrower,
20
        address _positionManager
21
    ) external view returns (PositionManagerApproval);
22

                            
                        
23
    function setPositionManagerApproval(
24
        address _positionManager,
25
        PositionManagerApproval _approval
26
    ) external;
27

                            
                        
28
    function revokePositionManagerApproval(address _positionManager) external;
29

                            
                        
30
    function renouncePositionManagerApproval(address _borrower) external;
31

                            
                        
32
    function permitPositionManagerApproval(
33
        address _borrower,
34
        address _positionManager,
35
        PositionManagerApproval _approval,
36
        uint _deadline,
37
        uint8 v,
38
        bytes32 r,
39
        bytes32 s
40
    ) external;
41

                            
                        
42
    function version() external view returns (string memory);
43

                            
                        
44
    function permitTypeHash() external view returns (bytes32);
45

                            
                        
46
    function domainSeparator() external view returns (bytes32);
47
}
48

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IPriceFeed {
6
    // --- Events ---
7
    event LastGoodPriceUpdated(uint256 _lastGoodPrice);
8
    event PriceFeedStatusChanged(Status newStatus);
9
    event FallbackCallerChanged(address _oldFallbackCaller, address _newFallbackCaller);
10
    event UnhealthyFallbackCaller(address _fallbackCaller, uint256 timestamp);
11

                            
                        
12
    // --- Structs ---
13

                            
                        
14
    struct ChainlinkResponse {
15
        uint80 roundEthBtcId;
16
        uint80 roundStEthEthId;
17
        uint256 answer;
18
        uint256 timestampEthBtc;
19
        uint256 timestampStEthEth;
20
        bool success;
21
    }
22

                            
                        
23
    struct FallbackResponse {
24
        uint256 answer;
25
        uint256 timestamp;
26
        bool success;
27
    }
28

                            
                        
29
    // --- Enum ---
30

                            
                        
31
    enum Status {
32
        chainlinkWorking,
33
        usingFallbackChainlinkUntrusted,
34
        bothOraclesUntrusted,
35
        usingFallbackChainlinkFrozen,
36
        usingChainlinkFallbackUntrusted
37
    }
38

                            
                        
39
    // --- Function ---
40
    function fetchPrice() external returns (uint256);
41
}
42

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
// Interface for State Updates that can trigger RM Liquidations
5
interface IRecoveryModeGracePeriod {
6
    event TCRNotified(uint256 TCR); /// NOTE: Mostly for debugging to ensure synch
7

                            
                        
8
    // NOTE: Ts is implicit in events (it's added by GETH)
9
    event GracePeriodStart();
10
    event GracePeriodEnd();
11
    event GracePeriodSet(uint256 _recoveryModeGracePeriod);
12

                            
                        
13
    function notifyStartGracePeriod(uint256 tcr) external;
14

                            
                        
15
    function notifyEndGracePeriod(uint256 tcr) external;
16
}
17

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
// Common interface for the SortedCdps Doubly Linked List.
6
interface ISortedCdps {
7
    // --- Events ---
8

                            
                        
9
    event NodeAdded(bytes32 _id, uint _NICR);
10
    event NodeRemoved(bytes32 _id);
11

                            
                        
12
    // --- Functions ---
13

                            
                        
14
    function remove(bytes32 _id) external;
15

                            
                        
16
    function batchRemove(bytes32[] memory _ids) external;
17

                            
                        
18
    function reInsert(bytes32 _id, uint256 _newICR, bytes32 _prevId, bytes32 _nextId) external;
19

                            
                        
20
    function contains(bytes32 _id) external view returns (bool);
21

                            
                        
22
    function isFull() external view returns (bool);
23

                            
                        
24
    function isEmpty() external view returns (bool);
25

                            
                        
26
    function getSize() external view returns (uint256);
27

                            
                        
28
    function getMaxSize() external view returns (uint256);
29

                            
                        
30
    function getFirst() external view returns (bytes32);
31

                            
                        
32
    function getLast() external view returns (bytes32);
33

                            
                        
34
    function getNext(bytes32 _id) external view returns (bytes32);
35

                            
                        
36
    function getPrev(bytes32 _id) external view returns (bytes32);
37

                            
                        
38
    function validInsertPosition(
39
        uint256 _ICR,
40
        bytes32 _prevId,
41
        bytes32 _nextId
42
    ) external view returns (bool);
43

                            
                        
44
    function findInsertPosition(
45
        uint256 _ICR,
46
        bytes32 _prevId,
47
        bytes32 _nextId
48
    ) external view returns (bytes32, bytes32);
49

                            
                        
50
    function insert(
51
        address owner,
52
        uint256 _ICR,
53
        bytes32 _prevId,
54
        bytes32 _nextId
55
    ) external returns (bytes32);
56

                            
                        
57
    function getOwnerAddress(bytes32 _id) external pure returns (address);
58

                            
                        
59
    function nonExistId() external view returns (bytes32);
60

                            
                        
61
    function cdpCountOf(address owner) external view returns (uint256);
62

                            
                        
63
    function getCdpCountOf(
64
        address owner,
65
        bytes32 startNodeId,
66
        uint maxNodes
67
    ) external view returns (uint256, bytes32);
68

                            
                        
69
    function getCdpsOf(address owner) external view returns (bytes32[] memory);
70

                            
                        
71
    function getAllCdpsOf(
72
        address owner,
73
        bytes32 startNodeId,
74
        uint maxNodes
75
    ) external view returns (bytes32[] memory, uint256, bytes32);
76

                            
                        
77
    function cdpOfOwnerByIndex(address owner, uint256 index) external view returns (bytes32);
78

                            
                        
79
    function cdpOfOwnerByIdx(
80
        address owner,
81
        uint256 index,
82
        bytes32 startNodeId,
83
        uint maxNodes
84
    ) external view returns (bytes32, bool);
85
}
86

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IWETH {
6
    function deposit() external payable;
7

                            
                        
8
    function withdraw(uint256) external;
9

                            
                        
10
    function transfer(address to, uint256 amount) external returns (bool);
11

                            
                        
12
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
13
}
14

                            
                        

Lines covered: 440 / 486 (90.5%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4
import "./Interfaces/ICdpManagerData.sol";
5
import "./Interfaces/ICollSurplusPool.sol";
6
import "./Interfaces/IEBTCToken.sol";
7
import "./Interfaces/ISortedCdps.sol";
8
import "./Dependencies/ICollateralTokenOracle.sol";
9
import "./CdpManagerStorage.sol";
10

                            
                        
11
contract LiquidationLibrary is CdpManagerStorage {
12
    constructor(
13
        address _borrowerOperationsAddress,
14
        address _collSurplusPool,
15
        address _ebtcToken,
16
        address _sortedCdps,
17
        address _activePool,
18
        address _priceFeed,
19
        address _collateral
20
    )
21
        CdpManagerStorage(
22
            address(0),
23
            address(0),
24
            _borrowerOperationsAddress,
25
            _collSurplusPool,
26
            _ebtcToken,
27
            _sortedCdps,
28
            _activePool,
29
            _priceFeed,
30
            _collateral
31
        )
32
    {}
33

                            
                        
34
    /// @notice Single CDP liquidation function (fully).
35
    /// @notice callable by anyone, attempts to liquidate the CdpId. Executes successfully if Cdp meets the conditions for liquidation (e.g. in Normal Mode, it liquidates if the Cdp's ICR < the system MCR).
36
    function liquidate(bytes32 _cdpId) external nonReentrantSelfAndBOps {
37
        _liquidateIndividualCdpSetup(_cdpId, 0, _cdpId, _cdpId);
38
    }
39

                            
                        
40
    // Single CDP liquidation function (partially).
41
    function partiallyLiquidate(
42
        bytes32 _cdpId,
43
        uint256 _partialAmount,
44
        bytes32 _upperPartialHint,
45
        bytes32 _lowerPartialHint
46
    ) external nonReentrantSelfAndBOps {
47
        _liquidateIndividualCdpSetup(_cdpId, _partialAmount, _upperPartialHint, _lowerPartialHint);
48
    }
49

                            
                        
50
    // Single CDP liquidation function.
51
    function _liquidateIndividualCdpSetup(
52
        bytes32 _cdpId,
53
        uint256 _partialAmount,
54
        bytes32 _upperPartialHint,
55
        bytes32 _lowerPartialHint
56
    ) internal {
57
        _requireCdpIsActive(_cdpId);
58

                            
                        
59
        _syncAccounting(_cdpId);
60

                            
                        
61
        uint256 _price = priceFeed.fetchPrice();
62

                            
                        
63
        // prepare local variables
64
        uint256 _ICR = getICR(_cdpId, _price); // @audit syncAccounting already called, guarenteed to be synced
65
        (uint256 _TCR, uint256 systemColl, uint256 systemDebt) = _getTCRWithSystemDebtAndCollShares(
66
            _price
67
        );
68

                            
                        
69
        // If CDP is above MCR
70
        if (_ICR >= MCR) {
71
            // We must be in RM
72
            require(
73
                _TCR < CCR && _ICR <= _TCR, /// @audit is <= more dangerous? Should we use _ICR <= CCR to allow any risky CDP being liquidated?
74
                "CdpManager: ICR is not below liquidation threshold in current mode"
75
            );
76

                            
                        
77
            // == Grace Period == //
78
            uint128 cachedLastGracePeriodStartTimestamp = lastGracePeriodStartTimestamp;
79
            require(
80
                cachedLastGracePeriodStartTimestamp != UNSET_TIMESTAMP,
81
                "CdpManager: Recovery Mode grace period not started"
82
            );
83
            require(
84
                block.timestamp > cachedLastGracePeriodStartTimestamp + recoveryModeGracePeriod,
85
                "CdpManager: Recovery mode grace period still in effect"
86
            );
87
        } // Implicit Else Case, Implies ICR < MRC, meaning the CDP is liquidatable
88

                            
                        
89
        bool _recoveryModeAtStart = _TCR < CCR ? true : false;
90
        LiquidationLocals memory _liqState = LiquidationLocals(
91
            _cdpId,
92
            _partialAmount,
93
            _price,
94
            _ICR,
95
            _upperPartialHint,
96
            _lowerPartialHint,
97
            (_recoveryModeAtStart),
98
            _TCR,
99
            0,
100
            0,
101
            0,
102
            0,
103
            0,
104
            false
105
        );
106

                            
                        
107
        LiquidationRecoveryModeLocals memory _rs = LiquidationRecoveryModeLocals(
108
            systemDebt,
109
            systemColl,
110
            0,
111
            0,
112
            0,
113
            _cdpId,
114
            _price,
115
            _ICR,
116
            0,
117
            0,
118
            false
119
        );
120

                            
                        
121
        _liquidateIndividualCdpSetupCDP(_liqState, _rs);
122
    }
123

                            
                        
124
    // liquidate given CDP by repaying debt in full or partially if its ICR is below MCR or TCR in recovery mode.
125
    // For partial liquidation, caller should use HintHelper smart contract to get correct hints for reinsertion into sorted CDP list
126
    function _liquidateIndividualCdpSetupCDP(
127
        LiquidationLocals memory _liqState,
128
        LiquidationRecoveryModeLocals memory _recoveryState
129
    ) internal {
130
        LiquidationValues memory liquidationValues;
131

                            
                        
132
        uint256 startingSystemDebt = _recoveryState.entireSystemDebt;
133
        uint256 startingSystemColl = _recoveryState.entireSystemColl;
134

                            
                        
135
        if (_liqState.partialAmount == 0) {
136
            (
137
                liquidationValues.debtToOffset,
138
                liquidationValues.totalCollToSendToLiquidator,
139
                liquidationValues.debtToRedistribute,
140
                liquidationValues.collReward,
141
                liquidationValues.collSurplus
142
            ) = _liquidateCdpInGivenMode(_liqState, _recoveryState);
143
        } else {
144
            (
145
                liquidationValues.debtToOffset,
146
                liquidationValues.totalCollToSendToLiquidator
147
            ) = _liquidateCDPPartially(_liqState);
148
            if (
149
                liquidationValues.totalCollToSendToLiquidator == 0 &&
150
                liquidationValues.debtToOffset == 0
151
            ) {
152
                // retry with fully liquidation
153
                (
154
                    liquidationValues.debtToOffset,
155
                    liquidationValues.totalCollToSendToLiquidator,
156
                    liquidationValues.debtToRedistribute,
157
                    liquidationValues.collReward,
158
                    liquidationValues.collSurplus
159
                ) = _liquidateCdpInGivenMode(_liqState, _recoveryState);
160
            }
161
        }
162

                            
                        
163
        _finalizeLiquidation(
164
            liquidationValues.debtToOffset,
165
            liquidationValues.totalCollToSendToLiquidator,
166
            liquidationValues.debtToRedistribute,
167
            liquidationValues.collReward,
168
            liquidationValues.collSurplus,
169
            startingSystemColl,
170
            startingSystemDebt,
171
            _liqState.price
172
        );
173
    }
174

                            
                        
175
    // liquidate (and close) the CDP from an external liquidator
176
    // this function would return the liquidated debt and collateral of the given CDP
177
    function _liquidateCdpInGivenMode(
178
        LiquidationLocals memory _liqState,
179
        LiquidationRecoveryModeLocals memory _recoveryState
180
    ) private returns (uint256, uint256, uint256, uint256, uint256) {
181
        if (_liqState.recoveryModeAtStart) {
182
            LiquidationRecoveryModeLocals
183
                memory _outputState = _liquidateIndividualCdpSetupCDPInRecoveryMode(_recoveryState);
184

                            
                        
185
            // housekeeping leftover collateral for liquidated CDP
186
            if (_outputState.totalColSurplus > 0) {
187
                activePool.transferSystemCollShares(
188
                    address(collSurplusPool),
189
                    _outputState.totalColSurplus
190
                );
191
            }
192

                            
                        
193
            return (
194
                _outputState.totalDebtToBurn,
195
                _outputState.totalColToSend,
196
                _outputState.totalDebtToRedistribute,
197
                _outputState.totalColReward,
198
                _outputState.totalColSurplus
199
            );
200
        } else {
201
            LiquidationLocals memory _outputState = _liquidateIndividualCdpSetupCDPInNormalMode(
202
                _liqState
203
            );
204
            return (
205
                _outputState.totalDebtToBurn,
206
                _outputState.totalColToSend,
207
                _outputState.totalDebtToRedistribute,
208
                _outputState.totalColReward,
209
                _outputState.totalColSurplus
210
            );
211
        }
212
    }
213

                            
                        
214
    function _liquidateIndividualCdpSetupCDPInNormalMode(
215
        LiquidationLocals memory _liqState
216
    ) private returns (LiquidationLocals memory) {
217
        // liquidate entire debt
218
        (
219
            uint256 _totalDebtToBurn,
220
            uint256 _totalColToSend,
221
            uint256 _liquidatorReward
222
        ) = _closeCdpByLiquidation(_liqState.cdpId, _liqState.sequenceLiq);
223
        uint256 _cappedColPortion;
224
        uint256 _collSurplus;
225
        uint256 _debtToRedistribute;
226
        address _borrower = sortedCdps.getOwnerAddress(_liqState.cdpId);
227

                            
                        
228
        // I don't see an issue emitting the CdpUpdated() event up here and avoiding this extra cache, any objections?
229
        emit CdpUpdated(
230
            _liqState.cdpId,
231
            _borrower,
232
            _totalDebtToBurn,
233
            _totalColToSend,
234
            0,
235
            0,
236
            0,
237
            CdpOperation.liquidateInNormalMode
238
        );
239

                            
                        
240
        {
241
            (_cappedColPortion, _collSurplus, _debtToRedistribute) = _calculateSurplusAndCap(
242
                _liqState.ICR,
243
                _liqState.price,
244
                _totalDebtToBurn,
245
                _totalColToSend,
246
                true
247
            );
248
            if (_collSurplus > 0) {
249
                // due to division precision loss, should be zero surplus in normal mode
250
                _cappedColPortion = _cappedColPortion + _collSurplus;
251
                _collSurplus = 0;
252
            }
253
            if (_debtToRedistribute > 0) {
254
                _totalDebtToBurn = _totalDebtToBurn - _debtToRedistribute;
255
            }
256
        }
257
        _liqState.totalDebtToBurn = _liqState.totalDebtToBurn + _totalDebtToBurn;
258
        _liqState.totalColToSend = _liqState.totalColToSend + _cappedColPortion;
259
        _liqState.totalDebtToRedistribute = _liqState.totalDebtToRedistribute + _debtToRedistribute;
260
        _liqState.totalColReward = _liqState.totalColReward + _liquidatorReward;
261

                            
                        
262
        // Emit events
263
        uint _debtToColl = (_totalDebtToBurn * 1e18) / _liqState.price;
264
        uint _cappedColl = collateral.getPooledEthByShares(_cappedColPortion + _liquidatorReward);
265

                            
                        
266
        emit CdpLiquidated(
267
            _liqState.cdpId,
268
            _borrower,
269
            _totalDebtToBurn,
270
            _cappedColPortion,
271
            CdpOperation.liquidateInNormalMode,
272
            msg.sender,
273
            _cappedColl > _debtToColl ? (_cappedColl - _debtToColl) : 0
274
        );
275

                            
                        
276
        return _liqState;
277
    }
278

                            
                        
279
    function _liquidateIndividualCdpSetupCDPInRecoveryMode(
280
        LiquidationRecoveryModeLocals memory _recoveryState
281
    ) private returns (LiquidationRecoveryModeLocals memory) {
282
        // liquidate entire debt
283
        (
284
            uint256 _totalDebtToBurn,
285
            uint256 _totalColToSend,
286
            uint256 _liquidatorReward
287
        ) = _closeCdpByLiquidation(_recoveryState.cdpId, _recoveryState.sequenceLiq);
288

                            
                        
289
        // cap the liquidated collateral if required
290
        uint256 _cappedColPortion;
291
        uint256 _collSurplus;
292
        uint256 _debtToRedistribute;
293
        address _borrower = sortedCdps.getOwnerAddress(_recoveryState.cdpId);
294

                            
                        
295
        // I don't see an issue emitting the CdpUpdated() event up here and avoiding an extra cache of the values, any objections?
296
        emit CdpUpdated(
297
            _recoveryState.cdpId,
298
            _borrower,
299
            _totalDebtToBurn,
300
            _totalColToSend,
301
            0,
302
            0,
303
            0,
304
            CdpOperation.liquidateInRecoveryMode
305
        );
306

                            
                        
307
        // avoid stack too deep
308
        {
309
            (_cappedColPortion, _collSurplus, _debtToRedistribute) = _calculateSurplusAndCap(
310
                _recoveryState.ICR,
311
                _recoveryState.price,
312
                _totalDebtToBurn,
313
                _totalColToSend,
314
                true
315
            );
316
            if (_collSurplus > 0) {
317
                collSurplusPool.increaseSurplusCollShares(_borrower, _collSurplus);
318
                _recoveryState.totalColSurplus = _recoveryState.totalColSurplus + _collSurplus;
319
            }
320
            if (_debtToRedistribute > 0) {
321
                _totalDebtToBurn = _totalDebtToBurn - _debtToRedistribute;
322
            }
323
        }
324
        _recoveryState.totalDebtToBurn = _recoveryState.totalDebtToBurn + _totalDebtToBurn;
325
        _recoveryState.totalColToSend = _recoveryState.totalColToSend + _cappedColPortion;
326
        _recoveryState.totalDebtToRedistribute =
327
            _recoveryState.totalDebtToRedistribute +
328
            _debtToRedistribute;
329
        _recoveryState.totalColReward = _recoveryState.totalColReward + _liquidatorReward;
330

                            
                        
331
        // check if system back to normal mode
332
        _recoveryState.entireSystemDebt = _recoveryState.entireSystemDebt > _totalDebtToBurn
333
            ? _recoveryState.entireSystemDebt - _totalDebtToBurn
334
            : 0;
335
        _recoveryState.entireSystemColl = _recoveryState.entireSystemColl > _totalColToSend
336
            ? _recoveryState.entireSystemColl - _totalColToSend
337
            : 0;
338

                            
                        
339
        uint _debtToColl = (_totalDebtToBurn * 1e18) / _recoveryState.price;
340
        uint _cappedColl = collateral.getPooledEthByShares(_cappedColPortion + _liquidatorReward);
341
        emit CdpLiquidated(
342
            _recoveryState.cdpId,
343
            _borrower,
344
            _totalDebtToBurn,
345
            _cappedColPortion,
346
            CdpOperation.liquidateInRecoveryMode,
347
            msg.sender,
348
            _cappedColl > _debtToColl ? (_cappedColl - _debtToColl) : 0
349
        );
350

                            
                        
351
        return _recoveryState;
352
    }
353

                            
                        
354
    // liquidate (and close) the CDP from an external liquidator
355
    // this function would return the liquidated debt and collateral of the given CDP
356
    // without emmiting events
357
    function _closeCdpByLiquidation(
358
        bytes32 _cdpId,
359
        bool _sequenceLiq
360
    ) private returns (uint256, uint256, uint256) {
361
        // calculate entire debt to repay
362
        (uint256 entireDebt, uint256 entireColl, ) = getDebtAndCollShares(_cdpId);
363

                            
                        
364
        // housekeeping after liquidation by closing the CDP
365
        _removeStake(_cdpId);
366
        uint256 _liquidatorReward = Cdps[_cdpId].liquidatorRewardShares;
367
        if (_sequenceLiq) {
368
            _closeCdpWithoutRemovingSortedCdps(_cdpId, Status.closedByLiquidation);
369
        } else {
370
            _closeCdp(_cdpId, Status.closedByLiquidation);
371
        }
372

                            
                        
373
        return (entireDebt, entireColl, _liquidatorReward);
374
    }
375

                            
                        
376
    // Liquidate partially the CDP by an external liquidator
377
    // This function would return the liquidated debt and collateral of the given CDP
378
    function _liquidateCDPPartially(
379
        LiquidationLocals memory _partialState
380
    ) private returns (uint256, uint256) {
381
        bytes32 _cdpId = _partialState.cdpId;
382
        uint256 _partialDebt = _partialState.partialAmount;
383

                            
                        
384
        // calculate entire debt to repay
385
        CdpDebtAndCollShares memory _debtAndColl = _getDebtAndCollShares(_cdpId);
386
        _requirePartialLiqDebtSize(_partialDebt, _debtAndColl.entireDebt, _partialState.price);
387
        uint256 newDebt = _debtAndColl.entireDebt - _partialDebt;
388

                            
                        
389
        // credit to https://arxiv.org/pdf/2212.07306.pdf for details
390
        (uint256 _partialColl, uint256 newColl, ) = _calculateSurplusAndCap(
391
            _partialState.ICR,
392
            _partialState.price,
393
            _partialDebt,
394
            _debtAndColl.entireColl,
395
            false
396
        );
397

                            
                        
398
        // early return: if new collateral is zero, we have a full liqudiation
399
        if (newColl == 0) {
400
            return (0, 0);
401
        }
402

                            
                        
403
        // If we have coll remaining, it must meet minimum CDP size requirements
404
        _requirePartialLiqCollSize(collateral.getPooledEthByShares(newColl));
405

                            
                        
406
        // updating the CDP accounting for partial liquidation
407
        _partiallyReduceCdpDebt(_cdpId, _partialDebt, _partialColl);
408

                            
                        
409
        // reInsert into sorted CDP list after partial liquidation
410
        {
411
            _reInsertPartialLiquidation(
412
                _partialState,
413
                LiquityMath._computeNominalCR(newColl, newDebt),
414
                _debtAndColl.entireDebt,
415
                _debtAndColl.entireColl
416
            );
417
            uint _debtToColl = (_partialDebt * 1e18) / _partialState.price;
418
            uint _cappedColl = collateral.getPooledEthByShares(_partialColl);
419
            emit CdpPartiallyLiquidated(
420
                _cdpId,
421
                sortedCdps.getOwnerAddress(_cdpId),
422
                _partialDebt,
423
                _partialColl,
424
                CdpOperation.partiallyLiquidate,
425
                msg.sender,
426
                _cappedColl > _debtToColl ? (_cappedColl - _debtToColl) : 0
427
            );
428
        }
429
        return (_partialDebt, _partialColl);
430
    }
431

                            
                        
432
    function _partiallyReduceCdpDebt(
433
        bytes32 _cdpId,
434
        uint256 _partialDebt,
435
        uint256 _partialColl
436
    ) internal {
437
        Cdp storage _cdp = Cdps[_cdpId];
438

                            
                        
439
        uint256 _coll = _cdp.coll;
440
        uint256 _debt = _cdp.debt;
441

                            
                        
442
        _cdp.coll = _coll - _partialColl;
443
        _cdp.debt = _debt - _partialDebt;
444
        _updateStakeAndTotalStakes(_cdpId);
445

                            
                        
446
        _updateRedistributedDebtSnapshot(_cdpId);
447
    }
448

                            
                        
449
    // Re-Insertion into SortedCdp list after partial liquidation
450
    function _reInsertPartialLiquidation(
451
        LiquidationLocals memory _partialState,
452
        uint256 _newNICR,
453
        uint256 _oldDebt,
454
        uint256 _oldColl
455
    ) internal {
456
        bytes32 _cdpId = _partialState.cdpId;
457

                            
                        
458
        // ensure new ICR does NOT decrease due to partial liquidation
459
        // if original ICR is above LICR
460
        if (_partialState.ICR > LICR) {
461
            require(getICR(_cdpId, _partialState.price) >= _partialState.ICR, "!_newICR>=_ICR");
462
        }
463

                            
                        
464
        // reInsert into sorted CDP list
465
        sortedCdps.reInsert(
466
            _cdpId,
467
            _newNICR,
468
            _partialState.upperPartialHint,
469
            _partialState.lowerPartialHint
470
        );
471
        emit CdpUpdated(
472
            _cdpId,
473
            sortedCdps.getOwnerAddress(_cdpId),
474
            _oldDebt,
475
            _oldColl,
476
            Cdps[_cdpId].debt,
477
            Cdps[_cdpId].coll,
478
            Cdps[_cdpId].stake,
479
            CdpOperation.partiallyLiquidate
480
        );
481
    }
482

                            
                        
483
    function _finalizeLiquidation(
484
        uint256 totalDebtToBurn,
485
        uint256 totalColToSend,
486
        uint256 totalDebtToRedistribute,
487
        uint256 totalColReward,
488
        uint256 totalColSurplus,
489
        uint256 systemInitialCollShares,
490
        uint256 systemInitialDebt,
491
        uint256 price
492
    ) internal {
493
        // update the staking and collateral snapshots
494
        _updateSystemSnapshotsExcludeCollRemainder(totalColToSend);
495

                            
                        
496
        emit Liquidation(totalDebtToBurn, totalColToSend, totalColReward);
497

                            
                        
498
        _syncGracePeriodForGivenValues(
499
            systemInitialCollShares - totalColToSend - totalColSurplus,
500
            systemInitialDebt - totalDebtToBurn,
501
            price
502
        );
503

                            
                        
504
        // redistribute debt if any
505
        if (totalDebtToRedistribute > 0) {
506
            _redistributeDebt(totalDebtToRedistribute);
507
        }
508

                            
                        
509
        // burn the debt from liquidator
510
        ebtcToken.burn(msg.sender, totalDebtToBurn);
511

                            
                        
512
        // offset debt from Active Pool
513
        activePool.decreaseSystemDebt(totalDebtToBurn);
514

                            
                        
515
        // CEI: ensure sending back collateral to liquidator is last thing to do
516
        activePool.transferSystemCollSharesAndLiquidatorReward(
517
            msg.sender,
518
            totalColToSend,
519
            totalColReward
520
        );
521
    }
522

                            
                        
523
    // Function that calculates the amount of collateral to send to liquidator (plus incentive) and the amount of collateral surplus
524
    function _calculateSurplusAndCap(
525
        uint256 _ICR,
526
        uint256 _price,
527
        uint256 _totalDebtToBurn,
528
        uint256 _totalColToSend,
529
        bool _fullLiquidation
530
    )
531
        private
532
        view
533
        returns (uint256 cappedColPortion, uint256 collSurplus, uint256 debtToRedistribute)
534
    {
535
        // Calculate liquidation incentive for liquidator:
536
        // If ICR is less than 103%: give away 103% worth of collateral to liquidator, i.e., repaidDebt * 103% / price
537
        // If ICR is more than 103%: give away min(ICR, 110%) worth of collateral to liquidator, i.e., repaidDebt * min(ICR, 110%) / price
538
        uint256 _incentiveColl;
539
        if (_ICR > LICR) {
540
            _incentiveColl = (_totalDebtToBurn * (_ICR > MCR ? MCR : _ICR)) / _price;
541
        } else {
542
            if (_fullLiquidation) {
543
                // for full liquidation, there would be some bad debt to redistribute
544
                _incentiveColl = collateral.getPooledEthByShares(_totalColToSend);
545
                uint256 _debtToRepay = (_incentiveColl * _price) / LICR;
546
                debtToRedistribute = _debtToRepay < _totalDebtToBurn
547
                    ? _totalDebtToBurn - _debtToRepay
548
                    : 0;
549
            } else {
550
                // for partial liquidation, new ICR would deteriorate
551
                // since we give more incentive (103%) than current _ICR allowed
552
                _incentiveColl = (_totalDebtToBurn * LICR) / _price;
553
            }
554
        }
555
        cappedColPortion = collateral.getSharesByPooledEth(_incentiveColl);
556
        cappedColPortion = cappedColPortion < _totalColToSend ? cappedColPortion : _totalColToSend;
557
        collSurplus = (cappedColPortion == _totalColToSend) ? 0 : _totalColToSend - cappedColPortion;
558
    }
559

                            
                        
560
    // --- Batch liquidation functions ---
561

                            
                        
562
    function _getLiquidationValuesNormalMode(
563
        uint256 _price,
564
        uint256 _TCR,
565
        LocalVariables_LiquidationSequence memory vars,
566
        LiquidationValues memory singleLiquidation,
567
        bool sequenceLiq
568
    ) internal {
569
        LiquidationLocals memory _liqState = LiquidationLocals(
570
            vars.cdpId,
571
            0,
572
            _price,
573
            vars.ICR,
574
            vars.cdpId,
575
            vars.cdpId,
576
            (false),
577
            _TCR,
578
            0,
579
            0,
580
            0,
581
            0,
582
            0,
583
            sequenceLiq
584
        );
585

                            
                        
586
        LiquidationLocals memory _outputState = _liquidateIndividualCdpSetupCDPInNormalMode(
587
            _liqState
588
        );
589

                            
                        
590
        singleLiquidation.entireCdpDebt = _outputState.totalDebtToBurn;
591
        singleLiquidation.debtToOffset = _outputState.totalDebtToBurn;
592
        singleLiquidation.totalCollToSendToLiquidator = _outputState.totalColToSend;
593
        singleLiquidation.collSurplus = _outputState.totalColSurplus;
594
        singleLiquidation.debtToRedistribute = _outputState.totalDebtToRedistribute;
595
        singleLiquidation.collReward = _outputState.totalColReward;
596
    }
597

                            
                        
598
    function _getLiquidationValuesRecoveryMode(
599
        uint256 _price,
600
        uint256 _systemDebt,
601
        uint256 _systemCollShares,
602
        LocalVariables_LiquidationSequence memory vars,
603
        LiquidationValues memory singleLiquidation,
604
        bool sequenceLiq
605
    ) internal {
606
        LiquidationRecoveryModeLocals memory _recState = LiquidationRecoveryModeLocals(
607
            _systemDebt,
608
            _systemCollShares,
609
            0,
610
            0,
611
            0,
612
            vars.cdpId,
613
            _price,
614
            vars.ICR,
615
            0,
616
            0,
617
            sequenceLiq
618
        );
619

                            
                        
620
        LiquidationRecoveryModeLocals
621
            memory _outputState = _liquidateIndividualCdpSetupCDPInRecoveryMode(_recState);
622

                            
                        
623
        singleLiquidation.entireCdpDebt = _outputState.totalDebtToBurn;
624
        singleLiquidation.debtToOffset = _outputState.totalDebtToBurn;
625
        singleLiquidation.totalCollToSendToLiquidator = _outputState.totalColToSend;
626
        singleLiquidation.collSurplus = _outputState.totalColSurplus;
627
        singleLiquidation.debtToRedistribute = _outputState.totalDebtToRedistribute;
628
        singleLiquidation.collReward = _outputState.totalColReward;
629
    }
630

                            
                        
631
    /*
632
     * Attempt to liquidate a custom list of cdps provided by the caller.
633

                            
                        
634
     callable by anyone, accepts a custom list of Cdps addresses as an argument. Steps through the provided list and attempts to liquidate every Cdp, until it reaches the end or it runs out of gas. A Cdp is liquidated only if it meets the conditions for liquidation. For a batch of 10 Cdps, the gas costs per liquidated Cdp are roughly between 75K-83K, for a batch of 50 Cdps between 54K-69K.
635
     */
636
    function batchLiquidateCdps(bytes32[] memory _cdpArray) external nonReentrantSelfAndBOps {
637
        require(
638
            _cdpArray.length != 0,
639
            "LiquidationLibrary: Calldata address array must not be empty"
640
        );
641

                            
                        
642
        LocalVariables_OuterLiquidationFunction memory vars;
643
        LiquidationTotals memory totals;
644

                            
                        
645
        // taking fee to avoid accounted for the calculation of the TCR
646
        _syncGlobalAccounting();
647

                            
                        
648
        vars.price = priceFeed.fetchPrice();
649
        (uint256 _TCR, uint256 systemColl, uint256 systemDebt) = _getTCRWithSystemDebtAndCollShares(
650
            vars.price
651
        );
652
        vars.recoveryModeAtStart = _TCR < CCR ? true : false;
653

                            
                        
654
        // Perform the appropriate liquidation sequence - tally values and obtain their totals.
655
        if (vars.recoveryModeAtStart) {
656
            totals = _getTotalFromBatchLiquidate_RecoveryMode(
657
                vars.price,
658
                systemColl,
659
                systemDebt,
660
                _cdpArray,
661
                false
662
            );
663
        } else {
664
            //  if !vars.recoveryModeAtStart
665
            totals = _getTotalsFromBatchLiquidate_NormalMode(vars.price, _TCR, _cdpArray, false);
666
        }
667

                            
                        
668
        require(totals.totalDebtInSequence > 0, "LiquidationLibrary: nothing to liquidate");
669

                            
                        
670
        // housekeeping leftover collateral for liquidated CDPs
671
        if (totals.totalCollSurplus > 0) {
672
            activePool.transferSystemCollShares(address(collSurplusPool), totals.totalCollSurplus);
673
        }
674

                            
                        
675
        _finalizeLiquidation(
676
            totals.totalDebtToOffset,
677
            totals.totalCollToSendToLiquidator,
678
            totals.totalDebtToRedistribute,
679
            totals.totalCollReward,
680
            totals.totalCollSurplus,
681
            systemColl,
682
            systemDebt,
683
            vars.price
684
        );
685
    }
686

                            
                        
687
    /*
688
     * This function is used when the batch liquidation sequence starts during Recovery Mode. However, it
689
     * handle the case where the system *leaves* Recovery Mode, part way through the liquidation sequence
690
     */
691
    function _getTotalFromBatchLiquidate_RecoveryMode(
692
        uint256 _price,
693
        uint256 _systemCollShares,
694
        uint256 _systemDebt,
695
        bytes32[] memory _cdpArray,
696
        bool sequenceLiq
697
    ) internal returns (LiquidationTotals memory totals) {
698
        LocalVariables_LiquidationSequence memory vars;
699
        LiquidationValues memory singleLiquidation;
700

                            
                        
701
        vars.backToNormalMode = false;
702
        vars.entireSystemDebt = _systemDebt;
703
        vars.entireSystemColl = _systemCollShares;
704
        uint256 _TCR = _computeTCRWithGivenSystemValues(
705
            vars.entireSystemColl,
706
            vars.entireSystemDebt,
707
            _price
708
        );
709
        uint256 _cnt = _cdpArray.length;
710
        bool[] memory _liqFlags = new bool[](_cnt);
711
        uint256 _liqCnt;
712
        uint256 _start = sequenceLiq ? _cnt - 1 : 0;
713
        for (vars.i = _start; ; ) {
714
            vars.cdpId = _cdpArray[vars.i];
715
            // only for active cdps
716
            if (vars.cdpId != bytes32(0) && Cdps[vars.cdpId].status == Status.active) {
717
                vars.ICR = getSyncedICR(vars.cdpId, _price);
718

                            
                        
719
                if (
720
                    !vars.backToNormalMode &&
721
                    (vars.ICR < MCR || canLiquidateRecoveryMode(vars.ICR, _TCR))
722
                ) {
723
                    vars.price = _price;
724
                    _syncAccounting(vars.cdpId);
725
                    _getLiquidationValuesRecoveryMode(
726
                        _price,
727
                        vars.entireSystemDebt,
728
                        vars.entireSystemColl,
729
                        vars,
730
                        singleLiquidation,
731
                        sequenceLiq
732
                    );
733

                            
                        
734
                    // Update aggregate trackers
735
                    vars.entireSystemDebt = vars.entireSystemDebt - singleLiquidation.debtToOffset;
736
                    vars.entireSystemColl =
737
                        vars.entireSystemColl -
738
                        singleLiquidation.totalCollToSendToLiquidator -
739
                        singleLiquidation.collSurplus;
740

                            
                        
741
                    // Add liquidation values to their respective running totals
742
                    totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
743

                            
                        
744
                    _TCR = _computeTCRWithGivenSystemValues(
745
                        vars.entireSystemColl,
746
                        vars.entireSystemDebt,
747
                        _price
748
                    );
749
                    vars.backToNormalMode = _TCR < CCR ? false : true;
750
                    _liqFlags[vars.i] = true;
751
                    _liqCnt += 1;
752
                } else if (vars.backToNormalMode && vars.ICR < MCR) {
753
                    _syncAccounting(vars.cdpId);
754
                    _getLiquidationValuesNormalMode(
755
                        _price,
756
                        _TCR,
757
                        vars,
758
                        singleLiquidation,
759
                        sequenceLiq
760
                    );
761

                            
                        
762
                    // Add liquidation values to their respective running totals
763
                    totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
764
                    _liqFlags[vars.i] = true;
765
                    _liqCnt += 1;
766
                }
767
                // In Normal Mode skip cdps with ICR >= MCR
768
            }
769
            if (sequenceLiq) {
770
                if (vars.i == 0) {
771
                    break;
772
                }
773
                --vars.i;
774
            } else {
775
                ++vars.i;
776
                if (vars.i == _cnt) {
777
                    break;
778
                }
779
            }
780
        }
781

                            
                        
782
        // remove from sortedCdps for sequence liquidation
783
        if (sequenceLiq) {
784
            bytes32[] memory _toRemoveIds = _cdpArray;
785
            if (_liqCnt > 0 && _liqCnt != _cnt) {
786
                _toRemoveIds = new bytes32[](_liqCnt);
787
                uint256 _j;
788
                for (uint256 i = 0; i < _cnt; ++i) {
789
                    if (_liqFlags[i]) {
790
                        _toRemoveIds[_j] = _cdpArray[i];
791
                        _j += 1;
792
                    }
793
                }
794
                require(
795
                    _j == _liqCnt,
796
                    "LiquidationLibrary: sequence liquidation (recovery mode) count error!"
797
                );
798
            }
799
            if (_liqCnt > 1) {
800
                sortedCdps.batchRemove(_toRemoveIds);
801
            } else if (_liqCnt == 1) {
802
                sortedCdps.remove(_toRemoveIds[0]);
803
            }
804
        }
805
    }
806

                            
                        
807
    function _getTotalsFromBatchLiquidate_NormalMode(
808
        uint256 _price,
809
        uint256 _TCR,
810
        bytes32[] memory _cdpArray,
811
        bool sequenceLiq
812
    ) internal returns (LiquidationTotals memory totals) {
813
        LocalVariables_LiquidationSequence memory vars;
814
        LiquidationValues memory singleLiquidation;
815
        uint256 _cnt = _cdpArray.length;
816
        uint256 _liqCnt;
817
        uint256 _start = sequenceLiq ? _cnt - 1 : 0;
818
        for (vars.i = _start; ; ) {
819
            vars.cdpId = _cdpArray[vars.i];
820
            // only for active cdps
821
            if (vars.cdpId != bytes32(0) && Cdps[vars.cdpId].status == Status.active) {
822
                vars.ICR = getSyncedICR(vars.cdpId, _price);
823

                            
                        
824
                if (vars.ICR < MCR) {
825
                    _syncAccounting(vars.cdpId);
826
                    _getLiquidationValuesNormalMode(
827
                        _price,
828
                        _TCR,
829
                        vars,
830
                        singleLiquidation,
831
                        sequenceLiq
832
                    );
833

                            
                        
834
                    // Add liquidation values to their respective running totals
835
                    totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
836
                    _liqCnt += 1;
837
                }
838
            }
839
            if (sequenceLiq) {
840
                if (vars.i == 0) {
841
                    break;
842
                }
843
                --vars.i;
844
            } else {
845
                ++vars.i;
846
                if (vars.i == _cnt) {
847
                    break;
848
                }
849
            }
850
        }
851

                            
                        
852
        // remove from sortedCdps for sequence liquidation
853
        if (sequenceLiq) {
854
            require(
855
                _liqCnt == _cnt,
856
                "LiquidationLibrary: sequence liquidation (normal mode) count error!"
857
            );
858
            if (_cnt > 1) {
859
                sortedCdps.batchRemove(_cdpArray);
860
            } else if (_cnt == 1) {
861
                sortedCdps.remove(_cdpArray[0]);
862
            }
863
        }
864
    }
865

                            
                        
866
    // --- Liquidation helper functions ---
867

                            
                        
868
    function _addLiquidationValuesToTotals(
869
        LiquidationTotals memory oldTotals,
870
        LiquidationValues memory singleLiquidation
871
    ) internal pure returns (LiquidationTotals memory newTotals) {
872
        // Tally all the values with their respective running totals
873
        newTotals.totalDebtInSequence =
874
            oldTotals.totalDebtInSequence +
875
            singleLiquidation.entireCdpDebt;
876
        newTotals.totalDebtToOffset = oldTotals.totalDebtToOffset + singleLiquidation.debtToOffset;
877
        newTotals.totalCollToSendToLiquidator =
878
            oldTotals.totalCollToSendToLiquidator +
879
            singleLiquidation.totalCollToSendToLiquidator;
880
        newTotals.totalDebtToRedistribute =
881
            oldTotals.totalDebtToRedistribute +
882
            singleLiquidation.debtToRedistribute;
883
        newTotals.totalCollSurplus = oldTotals.totalCollSurplus + singleLiquidation.collSurplus;
884
        newTotals.totalCollReward = oldTotals.totalCollReward + singleLiquidation.collReward;
885

                            
                        
886
        return newTotals;
887
    }
888

                            
                        
889
    function _redistributeDebt(uint256 _debt) internal {
890
        if (_debt == 0) {
891
            return;
892
        }
893

                            
                        
894
        /*
895
         * Add distributed debt rewards-per-unit-staked to the running totals. Division uses a "feedback"
896
         * error correction, to keep the cumulative error low in the running totals systemDebtRedistributionIndex:
897
         *
898
         * 1) Form numerators which compensate for the floor division errors that occurred the last time this
899
         * function was called.
900
         * 2) Calculate "per-unit-staked" ratios.
901
         * 3) Multiply each ratio back by its denominator, to reveal the current floor division error.
902
         * 4) Store these errors for use in the next correction when this function is called.
903
         * 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended.
904
         */
905
        uint256 EBTCDebtNumerator = (_debt * DECIMAL_PRECISION) + lastEBTCDebtErrorRedistribution;
906

                            
                        
907
        // Get the per-unit-staked terms
908
        uint256 _totalStakes = totalStakes;
909
        uint256 EBTCDebtRewardPerUnitStaked = EBTCDebtNumerator / _totalStakes;
910

                            
                        
911
        lastEBTCDebtErrorRedistribution =
912
            EBTCDebtNumerator -
913
            (EBTCDebtRewardPerUnitStaked * _totalStakes);
914

                            
                        
915
        // Add per-unit-staked terms to the running totals
916
        systemDebtRedistributionIndex = systemDebtRedistributionIndex + EBTCDebtRewardPerUnitStaked;
917

                            
                        
918
        emit SystemDebtRedistributionIndexUpdated(systemDebtRedistributionIndex);
919
    }
920

                            
                        
921
    // --- 'require' wrapper functions ---
922

                            
                        
923
    function _requirePartialLiqDebtSize(
924
        uint256 _partialDebt,
925
        uint256 _entireDebt,
926
        uint256 _price
927
    ) internal view {
928
        require(
929
            (_partialDebt + _convertDebtDenominationToBtc(MIN_NET_COLL, _price)) <= _entireDebt,
930
            "LiquidationLibrary: Partial debt liquidated must be less than total debt"
931
        );
932
    }
933

                            
                        
934
    function _requirePartialLiqCollSize(uint256 _entireColl) internal pure {
935
        require(
936
            _entireColl >= MIN_NET_COLL,
937
            "LiquidationLibrary: Coll remaining in partially liquidated CDP must be >= minimum"
938
        );
939
    }
940
}
941

                            
                        

Lines covered: 41 / 46 (89.1%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IPriceFeed.sol";
6
import "./Interfaces/ICdpManager.sol";
7
import "./Interfaces/ISortedCdps.sol";
8
import "./Interfaces/ICdpManagerData.sol";
9
import "./Dependencies/LiquityBase.sol";
10

                            
                        
11
/// @notice Helper to turn a sequence into CDP id array for batch liquidation
12
/// @dev Note this sequencer only serves as an approximation tool to provide "best-effort"
13
/// @dev that return a list of CDP ids which could be consumed by "CdpManager.batchLiquidateCdps()".
14
/// @dev It is possible that some of the returned CDPs might be skipped (not liquidatable any more)
15
/// @dev during liquidation execution due to change of the system states
16
/// @dev e.g., TCR brought back from Recovery Mode to Normal Mode
17
contract LiquidationSequencer is LiquityBase {
18
    ICdpManager public immutable cdpManager;
19
    ISortedCdps public immutable sortedCdps;
20

                            
                        
21
    constructor(
22
        address _cdpManagerAddress,
23
        address _sortedCdpsAddress,
24
        address _priceFeedAddress,
25
        address _activePoolAddress,
26
        address _collateralAddress
27
    ) LiquityBase(_activePoolAddress, _priceFeedAddress, _collateralAddress) {
28
        cdpManager = ICdpManager(_cdpManagerAddress);
29
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
30
    }
31

                            
                        
32
    /// @dev Get first N batch of liquidatable Cdps at current price
33
    /// @dev Non-view function that updates and returns live price at execution time
34
    /// @dev could use callStatic offline to save gas
35
    function sequenceLiqToBatchLiq(uint256 _n) external returns (bytes32[] memory _array) {
36
        uint256 _price = priceFeed.fetchPrice();
37
        return sequenceLiqToBatchLiqWithPrice(_n, _price);
38
    }
39

                            
                        
40
    /// @dev Get first N batch of liquidatable Cdps at specified price
41
    /// @dev Non-view function that will sync global state
42
    /// @dev could use callStatic offline to save gas
43
    function sequenceLiqToBatchLiqWithPrice(
44
        uint256 _n,
45
        uint256 _price
46
    ) public returns (bytes32[] memory _array) {
47
        cdpManager.syncGlobalAccountingAndGracePeriod();
48
        (uint256 _TCR, , ) = _getTCRWithSystemDebtAndCollShares(_price);
49
        return _sequenceLiqToBatchLiq(_n, _price, _TCR);
50
    }
51

                            
                        
52
    // return CdpId array (in NICR-decreasing order same as SortedCdps)
53
    // including the last N CDPs in sortedCdps for batch liquidation
54
    function _sequenceLiqToBatchLiq(
55
        uint256 _n,
56
        uint256 _price,
57
        uint256 _TCR
58
    ) internal view returns (bytes32[] memory _array) {
59
        if (_n > 0) {
60
            bool _recoveryMode = _TCR < CCR ? true : false;
61
            // get count of liquidatable CDPs with 1st iteration
62
            (uint256 _cnt, ) = _iterateOverSortedCdps(0, _TCR, _n, _price, _recoveryMode);
63

                            
                        
64
            // retrieve liquidatable CDPs with 2nd iteration
65
            (uint256 _j, bytes32[] memory _returnedArray) = _iterateOverSortedCdps(
66
                _cnt,
67
                _TCR,
68
                _n,
69
                _price,
70
                _recoveryMode
71
            );
72
            require(_j == _cnt, "LiquidationSequencer: wrong sequence conversion!");
73
            _array = _returnedArray;
74
        }
75
    }
76

                            
                        
77
    function _iterateOverSortedCdps(
78
        uint256 _realCount,
79
        uint256 _TCR,
80
        uint256 _n,
81
        uint256 _price,
82
        bool _recoveryMode
83
    ) internal view returns (uint256 _cnt, bytes32[] memory _array) {
84
        // if there is already a count (calculated from previous iteration)
85
        // we use the value to initialize CDP id array for return
86
        if (_realCount > 0) {
87
            _array = new bytes32[](_realCount);
88
        }
89

                            
                        
90
        // initialize variables for this iteration
91
        bytes32 _last = sortedCdps.getLast();
92
        bytes32 _first = sortedCdps.getFirst();
93
        bytes32 _cdpId = _last;
94

                            
                        
95
        for (uint256 i = 0; i < (_realCount > 0 ? _realCount : _n) && _cdpId != _first; ) {
96
            bool _liquidatable = _checkCdpLiquidability(_cdpId, _TCR, _price, _recoveryMode);
97
            if (_liquidatable) {
98
                if (_realCount > 0) {
99
                    _array[_realCount - _cnt - 1] = _cdpId;
100
                }
101
                unchecked {
102
                    ++_cnt;
103
                }
104
                _cdpId = sortedCdps.getPrev(_cdpId);
105
            } else {
106
                // breaking loop early if not liquidatable due to sorted (descending) list of CDPs
107
                break;
108
            }
109
            unchecked {
110
                ++i;
111
            }
112
        }
113
    }
114

                            
                        
115
    function _checkCdpLiquidability(
116
        bytes32 _cdpId,
117
        uint256 _TCR,
118
        uint256 _price,
119
        bool _recoveryMode
120
    ) internal view returns (bool) {
121
        uint256 _icr = cdpManager.getSyncedICR(_cdpId, _price);
122
        bool _liquidatable = _canLiquidateInCurrentMode(_recoveryMode, _icr, _TCR);
123
        return _liquidatable;
124
    }
125

                            
                        
126
    function _canLiquidateInCurrentMode(
127
        bool _recovery,
128
        uint256 _icr,
129
        uint256 _TCR
130
    ) internal view returns (bool) {
131
        bool _liquidatable = _recovery ? (_icr < MCR || _icr < _TCR) : _icr < MCR;
132

                            
                        
133
        return _liquidatable;
134
    }
135
}
136

                            
                        

Lines covered: 170 / 226 (75.2%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ISortedCdps.sol";
6
import "./Interfaces/ICdpManager.sol";
7
import "./Interfaces/IBorrowerOperations.sol";
8

                            
                        
9
/*
10
 * A sorted doubly linked list with nodes sorted in descending order.
11
 *
12
 * Nodes map to active Cdps in the system by ID.
13
 * Nodes are ordered according to their current nominal individual collateral ratio (NICR),
14
 * which is like the ICR but without the price, i.e., just collateral / debt.
15
 *
16
 * The list optionally accepts insert position hints.
17
 *
18
 * NICRs are computed dynamically at runtime, and not stored on the Node. This is because NICRs of active Cdps
19
 * change dynamically as liquidation events occur.
20
 *
21
 * The list relies on the fact that liquidation events preserve ordering: a liquidation decreases the NICRs of all active Cdps,
22
 * but maintains their order. A node inserted based on current NICR will maintain the correct position,
23
 * relative to it's peers, as rewards accumulate, as long as it's raw collateral and debt have not changed.
24
 * Thus, Nodes remain sorted by current NICR.
25
 *
26
 * Nodes need only be re-inserted upon a Cdp operation - when the owner adds or removes collateral or debt
27
 * to their position.
28
 *
29
 * The list is a modification of the following audited SortedDoublyLinkedList:
30
 * https://github.com/livepeer/protocol/blob/master/contracts/libraries/SortedDoublyLL.sol
31
 *
32
 *
33
 * Changes made in the Liquity implementation:
34
 *
35
 * - Keys have been removed from nodes
36
 *
37
 * - Ordering checks for insertion are performed by comparing an NICR argument to the current NICR, calculated at runtime.
38
 *   The list relies on the property that ordering by ICR is maintained as the stETH:BTC price varies.
39
 *
40
 * - Public functions with parameters have been made internal to save gas, and given an external wrapper function for external access
41
 */
42
contract SortedCdps is ISortedCdps {
43
    string public constant NAME = "SortedCdps";
44

                            
                        
45
    address public immutable borrowerOperationsAddress;
46

                            
                        
47
    ICdpManager public immutable cdpManager;
48

                            
                        
49
    uint256 public immutable maxSize;
50

                            
                        
51
    uint256 constant ADDRESS_SHIFT = 96; // 8 * 12; Puts the address at leftmost bytes32 position
52
    uint256 constant BLOCK_SHIFT = 64; // 8 * 8; Puts the block value after the address
53

                            
                        
54
    // Information for a node in the list
55
    struct Node {
56
        bytes32 nextId; // Id of next node (smaller NICR) in the list
57
        bytes32 prevId; // Id of previous node (larger NICR) in the list
58
    }
59

                            
                        
60
    // Information for the list
61
    struct Data {
62
        bytes32 head; // Head of the list. Also the node in the list with the largest NICR
63
        bytes32 tail; // Tail of the list. Also the node in the list with the smallest NICR
64
        uint256 size; // Current size of the list
65
        mapping(bytes32 => Node) nodes; // Track the corresponding ids for each node in the list
66
    }
67

                            
                        
68
    Data public data;
69

                            
                        
70
    uint256 public nextCdpNonce;
71
    bytes32 public constant dummyId =
72
        0x0000000000000000000000000000000000000000000000000000000000000000;
73

                            
                        
74
    // --- Dependency setters ---
75
    constructor(uint256 _size, address _cdpManagerAddress, address _borrowerOperationsAddress) {
76
        if (_size == 0) {
77
            _size = type(uint256).max;
78
        }
79

                            
                        
80
        maxSize = _size;
81

                            
                        
82
        cdpManager = ICdpManager(_cdpManagerAddress);
83
        borrowerOperationsAddress = _borrowerOperationsAddress;
84
    }
85

                            
                        
86
    // https://github.com/balancer-labs/balancer-v2-monorepo/blob/18bd5fb5d87b451cc27fbd30b276d1fb2987b529/pkg/vault/contracts/PoolRegistry.sol
87
    function toCdpId(
88
        address owner,
89
        uint256 blockHeight,
90
        uint256 nonce
91
    ) public pure returns (bytes32) {
92
        bytes32 serialized;
93

                            
                        
94
        serialized |= bytes32(nonce);
95
        serialized |= bytes32(blockHeight) << BLOCK_SHIFT; // to accommendate more than 4.2 billion blocks
96
        serialized |= bytes32(uint256(uint160(owner))) << ADDRESS_SHIFT;
97

                            
                        
98
        return serialized;
99
    }
100

                            
                        
101
    /// @notice Get owner address of a given Cdp, by CdpId.
102
    /// @dev The owner address is stored in the first 20 bytes of the CdpId
103
    /// @param cdpId cdpId of Cdp to get owner of
104
    /// @return owner address of the Cdp
105
    function getOwnerAddress(bytes32 cdpId) public pure override returns (address) {
106
        uint256 _tmp = uint256(cdpId) >> ADDRESS_SHIFT;
107
        return address(uint160(_tmp));
108
    }
109

                            
                        
110
    function nonExistId() public pure override returns (bytes32) {
111
        return dummyId;
112
    }
113

                            
                        
114
    /// @notice Find a specific Cdp for a given owner, indexed by it's place in the linked list relative to other Cdps owned by the same address
115
    /// @notice Reverts if the index exceeds the number of active Cdps owned by the given owner
116
    /// @dev Intended for off-chain use, O(n) operation on size of SortedCdps linked list
117
    /// @param owner address of Cdp owner
118
    /// @param index index of Cdp, ordered by position in linked list relative to Cdps of the same owner
119
    function cdpOfOwnerByIndex(
120
        address owner,
121
        uint256 index
122
    ) external view override returns (bytes32) {
123
        (bytes32 _cdpId, ) = _cdpOfOwnerByIndex(owner, index, dummyId, 0);
124
        return _cdpId;
125
    }
126

                            
                        
127
    /// @dev a pagination-flavor search (from least ICR to biggest ICR) for CDP owned by given owner and specified index (starting at given CDP)
128
    /// @param startNodeId the seach traversal will start at this given CDP instead of the tail of the list
129
    /// @param maxNodes the traversal will go through the list by this given maximum limit of number of CDPs
130
    function cdpOfOwnerByIdx(
131
        address owner,
132
        uint256 index,
133
        bytes32 startNodeId,
134
        uint maxNodes
135
    ) external view override returns (bytes32, bool) {
136
        return _cdpOfOwnerByIndex(owner, index, startNodeId, maxNodes);
137
    }
138

                            
                        
139
    /// @dev return EITHER the found CDP owned by given owner & index with a true indicator OR
140
    /// @dev        current lastly-visited CDP as the startNode for next pagination with a false indicator
141
    function _cdpOfOwnerByIndex(
142
        address owner,
143
        uint256 index,
144
        bytes32 startNodeId,
145
        uint maxNodes
146
    ) internal view returns (bytes32, bool) {
147
        // walk the list, until we get to the indexed CDP
148
        // start at the given node or from the tail of list
149
        bytes32 _currentCdpId = (startNodeId == dummyId ? data.tail : startNodeId);
150
        uint _currentIndex = 0;
151
        uint i;
152

                            
                        
153
        while (_currentCdpId != dummyId) {
154
            // if the current Cdp is owned by specified owner
155
            if (getOwnerAddress(_currentCdpId) == owner) {
156
                // if the current index of the owner Cdp matches specified index
157
                if (_currentIndex == index) {
158
                    return (_currentCdpId, true);
159
                } else {
160
                    // if not, increment the owner index as we've seen a Cdp owned by them
161
                    _currentIndex = _currentIndex + 1;
162
                }
163
            }
164
            ++i;
165

                            
                        
166
            // move to the next Cdp in the list
167
            _currentCdpId = data.nodes[_currentCdpId].prevId;
168

                            
                        
169
            // cut the run if we exceed expected iterations through the loop
170
            if (maxNodes > 0 && i >= maxNodes) {
171
                break;
172
            }
173
        }
174
        // if we reach maximum iteration or end of list
175
        // without seeing the specified index for the owner
176
        // then maybe a new pagination is needed
177
        return (_currentCdpId, false);
178
    }
179

                            
                        
180
    /// @notice Get active Cdp count of a given address
181
    /// @dev Intended for off-chain use, O(n) operation on size of linked list
182
    function cdpCountOf(address owner) external view override returns (uint256) {
183
        (uint256 _cnt, ) = _cdpCountOf(owner, dummyId, 0);
184
        return _cnt;
185
    }
186

                            
                        
187
    /// @dev a pagination-flavor search count of (from least ICR to biggest ICR) CDPs owned by given owner (starting at given CDP)
188
    /// @param startNodeId the count traversal will start at this given CDP instead of the tail of the list
189
    /// @param maxNodes the traversal will go through the list by this given maximum limit of number of CDPs
190
    function getCdpCountOf(
191
        address owner,
192
        bytes32 startNodeId,
193
        uint maxNodes
194
    ) external view override returns (uint256, bytes32) {
195
        return _cdpCountOf(owner, startNodeId, maxNodes);
196
    }
197

                            
                        
198
    /// @dev return the found CDP count owned by given owner with
199
    /// @dev        current lastly-visited CDP as the startNode for next pagination
200
    function _cdpCountOf(
201
        address owner,
202
        bytes32 startNodeId,
203
        uint maxNodes
204
    ) internal view returns (uint256, bytes32) {
205
        // walk the list, until we get to the count
206
        // start at the given node or from the tail of list
207
        bytes32 _currentCdpId = (startNodeId == dummyId ? data.tail : startNodeId);
208
        uint _ownedCount = 0;
209
        uint i = 0;
210

                            
                        
211
        while (_currentCdpId != dummyId) {
212
            // if the current Cdp is owned by specified owner
213
            if (getOwnerAddress(_currentCdpId) == owner) {
214
                _ownedCount = _ownedCount + 1;
215
            }
216
            ++i;
217

                            
                        
218
            // move to the next Cdp in the list
219
            _currentCdpId = data.nodes[_currentCdpId].prevId;
220

                            
                        
221
            // cut the run if we exceed expected iterations through the loop
222
            if (maxNodes > 0 && i >= maxNodes) {
223
                break;
224
            }
225
        }
226
        return (_ownedCount, _currentCdpId);
227
    }
228

                            
                        
229
    /// @notice Get all active Cdps for a given address
230
    /// @dev Intended for off-chain use, O(n) operation on size of linked list
231
    function getCdpsOf(address owner) external view override returns (bytes32[] memory cdps) {
232
        // Naive method uses two-pass strategy to determine exactly how many Cdps are owned by owner
233
        // This roughly halves the amount of Cdps we can process before relying on pagination or off-chain methods
234
        (uint _ownedCount, ) = _cdpCountOf(owner, dummyId, 0);
235
        if (_ownedCount > 0) {
236
            (bytes32[] memory _allCdps, , ) = _getCdpsOf(owner, dummyId, 0, _ownedCount);
237
            cdps = _allCdps;
238
        }
239
    }
240

                            
                        
241
    /// @dev a pagination-flavor search retrieval of (from least ICR to biggest ICR) CDPs owned by given owner (starting at given CDP)
242
    /// @param startNodeId the traversal will start at this given CDP instead of the tail of the list
243
    /// @param maxNodes the traversal will go through the list by this given maximum limit of number of CDPs
244
    function getAllCdpsOf(
245
        address owner,
246
        bytes32 startNodeId,
247
        uint maxNodes
248
    ) external view override returns (bytes32[] memory, uint256, bytes32) {
249
        // Naive method uses two-pass strategy to determine exactly how many Cdps are owned by owner
250
        // This roughly halves the amount of Cdps we can process before relying on pagination or off-chain methods
251
        (uint _ownedCount, ) = _cdpCountOf(owner, startNodeId, maxNodes);
252
        return _getCdpsOf(owner, startNodeId, maxNodes, _ownedCount);
253
    }
254

                            
                        
255
    /// @dev return EITHER the found CDPs (also the count) owned by given owner OR empty array with
256
    /// @dev        current lastly-visited CDP as the startNode for next pagination
257
    function _getCdpsOf(
258
        address owner,
259
        bytes32 startNodeId,
260
        uint maxNodes,
261
        uint maxArraySize
262
    ) internal view returns (bytes32[] memory, uint256, bytes32) {
263
        if (maxArraySize == 0) {
264
            return (new bytes32[](0), 0, dummyId);
265
        }
266

                            
                        
267
        // Two-pass strategy, halving the amount of Cdps we can process before relying on pagination or off-chain methods
268
        bytes32[] memory userCdps = new bytes32[](maxArraySize);
269
        uint i = 0;
270
        uint _cdpRetrieved;
271

                            
                        
272
        // walk the list, until we get to the index
273
        // start at the given node or from the tail of list
274
        bytes32 _currentCdpId = (startNodeId == dummyId ? data.tail : startNodeId);
275

                            
                        
276
        while (_currentCdpId != dummyId) {
277
            // if the current Cdp is owned by specified owner
278
            if (getOwnerAddress(_currentCdpId) == owner) {
279
                userCdps[_cdpRetrieved] = _currentCdpId;
280
                ++_cdpRetrieved;
281
            }
282
            ++i;
283

                            
                        
284
            // move to the next Cdp in the list
285
            _currentCdpId = data.nodes[_currentCdpId].prevId;
286

                            
                        
287
            // cut the run if we exceed expected iterations through the loop
288
            if (maxNodes > 0 && i >= maxNodes) {
289
                break;
290
            }
291
        }
292

                            
                        
293
        return (userCdps, _cdpRetrieved, _currentCdpId);
294
    }
295

                            
                        
296
    /*
297
     * @dev Add a node to the list
298
     * @param owner cdp owner
299
     * @param _NICR Node's NICR
300
     * @param _prevId Id of previous node for the insert position
301
     * @param _nextId Id of next node for the insert position
302
     */
303
    function insert(
304
        address owner,
305
        uint256 _NICR,
306
        bytes32 _prevId,
307
        bytes32 _nextId
308
    ) external override returns (bytes32) {
309
        _requireCallerIsBOorCdpM();
310
        bytes32 _id = toCdpId(owner, block.number, nextCdpNonce);
311
        require(cdpManager.getCdpStatus(_id) == 0, "SortedCdps: new id is NOT nonExistent!");
312

                            
                        
313
        _insert(_id, _NICR, _prevId, _nextId);
314

                            
                        
315
        unchecked {
316
            ++nextCdpNonce;
317
        }
318

                            
                        
319
        return _id;
320
    }
321

                            
                        
322
    function _insert(bytes32 _id, uint256 _NICR, bytes32 _prevId, bytes32 _nextId) internal {
323
        // List must not be full
324
        require(!isFull(), "SortedCdps: List is full");
325
        // List must not already contain node
326
        require(!contains(_id), "SortedCdps: List already contains the node");
327
        // Node id must not be null
328
        require(_id != dummyId, "SortedCdps: Id cannot be zero");
329
        // NICR must be non-zero
330
        require(_NICR > 0, "SortedCdps: NICR must be positive");
331

                            
                        
332
        bytes32 prevId = _prevId;
333
        bytes32 nextId = _nextId;
334

                            
                        
335
        if (!_validInsertPosition(_NICR, prevId, nextId)) {
336
            // Sender's hint was not a valid insert position
337
            // Use sender's hint to find a valid insert position
338
            (prevId, nextId) = _findInsertPosition(_NICR, prevId, nextId);
339
        }
340

                            
                        
341
        if (prevId == dummyId && nextId == dummyId) {
342
            // Insert as head and tail
343
            data.head = _id;
344
            data.tail = _id;
345
        } else if (prevId == dummyId) {
346
            // Insert before `prevId` as the head
347
            data.nodes[_id].nextId = data.head;
348
            data.nodes[data.head].prevId = _id;
349
            data.head = _id;
350
        } else if (nextId == dummyId) {
351
            // Insert after `nextId` as the tail
352
            data.nodes[_id].prevId = data.tail;
353
            data.nodes[data.tail].nextId = _id;
354
            data.tail = _id;
355
        } else {
356
            // Insert at insert position between `prevId` and `nextId`
357
            data.nodes[_id].nextId = nextId;
358
            data.nodes[_id].prevId = prevId;
359
            data.nodes[prevId].nextId = _id;
360
            data.nodes[nextId].prevId = _id;
361
        }
362

                            
                        
363
        data.size = data.size + 1;
364
        emit NodeAdded(_id, _NICR);
365
    }
366

                            
                        
367
    function remove(bytes32 _id) external override {
368
        _requireCallerIsCdpManager();
369
        _remove(_id);
370
    }
371

                            
                        
372
    function batchRemove(bytes32[] memory _ids) external override {
373
        _requireCallerIsCdpManager();
374
        uint256 _len = _ids.length;
375
        require(_len > 1, "SortedCdps: batchRemove() only apply to multiple cdpIds!");
376

                            
                        
377
        bytes32 _firstPrev = data.nodes[_ids[0]].prevId;
378
        bytes32 _lastNext = data.nodes[_ids[_len - 1]].nextId;
379

                            
                        
380
        require(
381
            _firstPrev != dummyId || _lastNext != dummyId,
382
            "SortedCdps: batchRemove() leave ZERO node left!"
383
        );
384

                            
                        
385
        for (uint256 i = 0; i < _len; ++i) {
386
            require(contains(_ids[i]), "SortedCdps: List does not contain the id");
387
        }
388

                            
                        
389
        // orphan nodes in between to save gas
390
        if (_firstPrev != dummyId) {
391
            data.nodes[_firstPrev].nextId = _lastNext;
392
        } else {
393
            data.head = _lastNext;
394
        }
395
        if (_lastNext != dummyId) {
396
            data.nodes[_lastNext].prevId = _firstPrev;
397
        } else {
398
            data.tail = _firstPrev;
399
        }
400

                            
                        
401
        // delete node & owner storages to get gas refund
402
        for (uint i = 0; i < _len; ++i) {
403
            delete data.nodes[_ids[i]];
404
            emit NodeRemoved(_ids[i]);
405
        }
406
        data.size = data.size - _len;
407
    }
408

                            
                        
409
    /*
410
     * @dev Remove a node from the list
411
     * @param _id Node's id
412
     */
413
    function _remove(bytes32 _id) internal {
414
        // List must contain the node
415
        require(contains(_id), "SortedCdps: List does not contain the id");
416

                            
                        
417
        if (data.size > 1) {
418
            // List contains more than a single node
419
            if (_id == data.head) {
420
                // The removed node is the head
421
                // Set head to next node
422
                data.head = data.nodes[_id].nextId;
423
                // Set prev pointer of new head to null
424
                data.nodes[data.head].prevId = dummyId;
425
            } else if (_id == data.tail) {
426
                // The removed node is the tail
427
                // Set tail to previous node
428
                data.tail = data.nodes[_id].prevId;
429
                // Set next pointer of new tail to null
430
                data.nodes[data.tail].nextId = dummyId;
431
            } else {
432
                // The removed node is neither the head nor the tail
433
                // Set next pointer of previous node to the next node
434
                data.nodes[data.nodes[_id].prevId].nextId = data.nodes[_id].nextId;
435
                // Set prev pointer of next node to the previous node
436
                data.nodes[data.nodes[_id].nextId].prevId = data.nodes[_id].prevId;
437
            }
438
        } else {
439
            // List contains a single node
440
            // Set the head and tail to null
441
            data.head = dummyId;
442
            data.tail = dummyId;
443
        }
444

                            
                        
445
        delete data.nodes[_id];
446
        data.size = data.size - 1;
447
        emit NodeRemoved(_id);
448
    }
449

                            
                        
450
    /*
451
     * @dev Re-insert the node at a new position, based on its new NICR
452
     * @param _id Node's id
453
     * @param _newNICR Node's new NICR
454
     * @param _prevId Id of previous node for the new insert position
455
     * @param _nextId Id of next node for the new insert position
456
     */
457
    function reInsert(
458
        bytes32 _id,
459
        uint256 _newNICR,
460
        bytes32 _prevId,
461
        bytes32 _nextId
462
    ) external override {
463
        _requireCallerIsBOorCdpM();
464
        // List must contain the node
465
        require(contains(_id), "SortedCdps: List does not contain the id");
466
        // NICR must be non-zero
467
        require(_newNICR > 0, "SortedCdps: NICR must be positive");
468

                            
                        
469
        // Remove node from the list
470
        _remove(_id);
471

                            
                        
472
        _insert(_id, _newNICR, _prevId, _nextId);
473
    }
474

                            
                        
475
    /**
476
     * @dev Checks if the list contains a given node
477
     * @param _id The ID of the node
478
     * @return true if the node exists, false otherwise
479
     */
480
    function contains(bytes32 _id) public view override returns (bool) {
481
        bool _exist = _id != dummyId && (data.head == _id || data.tail == _id);
482
        if (!_exist) {
483
            Node memory _node = data.nodes[_id];
484
            _exist = _id != dummyId && (_node.nextId != dummyId && _node.prevId != dummyId);
485
        }
486
        return _exist;
487
    }
488

                            
                        
489
    /**
490
     * @dev Checks if the list is full
491
     * @return true if the list is full, false otherwise
492
     */
493
    function isFull() public view override returns (bool) {
494
        return data.size == maxSize;
495
    }
496

                            
                        
497
    /**
498
     * @dev Checks if the list is empty
499
     * @return true if the list is empty, false otherwise
500
     */
501
    function isEmpty() public view override returns (bool) {
502
        return data.size == 0;
503
    }
504

                            
                        
505
    /**
506
     * @dev Returns the current size of the list
507
     * @return The current size of the list
508
     */
509
    function getSize() external view override returns (uint256) {
510
        return data.size;
511
    }
512

                            
                        
513
    /**
514
     * @dev Returns the maximum size of the list
515
     * @return The maximum size of the list
516
     */
517
    function getMaxSize() external view override returns (uint256) {
518
        return maxSize;
519
    }
520

                            
                        
521
    /**
522
     * @dev Returns the first node in the list (node with the largest NICR)
523
     * @return The ID of the first node
524
     */
525
    function getFirst() external view override returns (bytes32) {
526
        return data.head;
527
    }
528

                            
                        
529
    /**
530
     * @dev Returns the last node in the list (node with the smallest NICR)
531
     * @return The ID of the last node
532
     */
533
    function getLast() external view override returns (bytes32) {
534
        return data.tail;
535
    }
536

                            
                        
537
    /**
538
     * @dev Returns the next node (with a smaller NICR) in the list for a given node
539
     * @param _id The ID of the node
540
     * @return The ID of the next node
541
     */
542
    function getNext(bytes32 _id) external view override returns (bytes32) {
543
        return data.nodes[_id].nextId;
544
    }
545

                            
                        
546
    /**
547
     * @dev Returns the previous node (with a larger NICR) in the list for a given node
548
     * @param _id The ID of the node
549
     * @return The ID of the previous node
550
     */
551
    function getPrev(bytes32 _id) external view override returns (bytes32) {
552
        return data.nodes[_id].prevId;
553
    }
554

                            
                        
555
    /*
556
     * @dev Check if a pair of nodes is a valid insertion point for a new node with the given NICR
557
     * @param _NICR Node's NICR
558
     * @param _prevId Id of previous node for the insert position
559
     * @param _nextId Id of next node for the insert position
560
     * @return true if the position is valid, false otherwise
561
     */
562
    function validInsertPosition(
563
        uint256 _NICR,
564
        bytes32 _prevId,
565
        bytes32 _nextId
566
    ) external view override returns (bool) {
567
        return _validInsertPosition(_NICR, _prevId, _nextId);
568
    }
569

                            
                        
570
    function _validInsertPosition(
571
        uint256 _NICR,
572
        bytes32 _prevId,
573
        bytes32 _nextId
574
    ) internal view returns (bool) {
575
        if (_prevId == dummyId && _nextId == dummyId) {
576
            // `(null, null)` is a valid insert position if the list is empty
577
            return isEmpty();
578
        } else if (_prevId == dummyId) {
579
            // `(null, _nextId)` is a valid insert position if `_nextId` is the head of the list
580
            return data.head == _nextId && _NICR >= cdpManager.getNominalICR(_nextId);
581
        } else if (_nextId == dummyId) {
582
            // `(_prevId, null)` is a valid insert position if `_prevId` is the tail of the list
583
            return data.tail == _prevId && _NICR <= cdpManager.getNominalICR(_prevId);
584
        } else {
585
            // `(_prevId, _nextId)` is a valid insert position if they are adjacent nodes and `_NICR` falls between the two nodes' NICRs
586
            return
587
                data.nodes[_prevId].nextId == _nextId &&
588
                cdpManager.getNominalICR(_prevId) >= _NICR &&
589
                _NICR >= cdpManager.getNominalICR(_nextId);
590
        }
591
    }
592

                            
                        
593
    /*
594
     * @dev Descend the list (larger NICRs to smaller NICRs) to find a valid insert position
595
     * @param _NICR Node's NICR
596
     * @param _startId Id of node to start descending the list from
597
     */
598
    function _descendList(uint256 _NICR, bytes32 _startId) internal view returns (bytes32, bytes32) {
599
        // If `_startId` is the head, check if the insert position is before the head
600
        if (data.head == _startId && _NICR >= cdpManager.getNominalICR(_startId)) {
601
            return (dummyId, _startId);
602
        }
603

                            
                        
604
        bytes32 prevId = _startId;
605
        bytes32 nextId = data.nodes[prevId].nextId;
606

                            
                        
607
        // Descend the list until we reach the end or until we find a valid insert position
608
        while (prevId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) {
609
            prevId = data.nodes[prevId].nextId;
610
            nextId = data.nodes[prevId].nextId;
611
        }
612

                            
                        
613
        return (prevId, nextId);
614
    }
615

                            
                        
616
    /*
617
     * @dev Ascend the list (smaller NICRs to larger NICRs) to find a valid insert position
618
     * @param _NICR Node's NICR
619
     * @param _startId Id of node to start ascending the list from
620
     */
621
    function _ascendList(uint256 _NICR, bytes32 _startId) internal view returns (bytes32, bytes32) {
622
        // If `_startId` is the tail, check if the insert position is after the tail
623
        if (data.tail == _startId && _NICR <= cdpManager.getNominalICR(_startId)) {
624
            return (_startId, dummyId);
625
        }
626

                            
                        
627
        bytes32 nextId = _startId;
628
        bytes32 prevId = data.nodes[nextId].prevId;
629

                            
                        
630
        // Ascend the list until we reach the end or until we find a valid insertion point
631
        while (nextId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) {
632
            nextId = data.nodes[nextId].prevId;
633
            prevId = data.nodes[nextId].prevId;
634
        }
635

                            
                        
636
        return (prevId, nextId);
637
    }
638

                            
                        
639
    /*
640
     * @dev Find the insert position for a new node with the given NICR
641
     * @param _NICR Node's NICR
642
     * @param _prevId Id of previous node for the insert position
643
     * @param _nextId Id of next node for the insert position
644
     * @return The IDs of the previous and next nodes for the insert position
645
     */
646
    function findInsertPosition(
647
        uint256 _NICR,
648
        bytes32 _prevId,
649
        bytes32 _nextId
650
    ) external view override returns (bytes32, bytes32) {
651
        return _findInsertPosition(_NICR, _prevId, _nextId);
652
    }
653

                            
                        
654
    function _findInsertPosition(
655
        uint256 _NICR,
656
        bytes32 _prevId,
657
        bytes32 _nextId
658
    ) internal view returns (bytes32, bytes32) {
659
        bytes32 prevId = _prevId;
660
        bytes32 nextId = _nextId;
661

                            
                        
662
        if (prevId != dummyId) {
663
            if (!contains(prevId) || _NICR > cdpManager.getNominalICR(prevId)) {
664
                // `prevId` does not exist anymore or now has a smaller NICR than the given NICR
665
                prevId = dummyId;
666
            }
667
        }
668

                            
                        
669
        if (nextId != dummyId) {
670
            if (!contains(nextId) || _NICR < cdpManager.getNominalICR(nextId)) {
671
                // `nextId` does not exist anymore or now has a larger NICR than the given NICR
672
                nextId = dummyId;
673
            }
674
        }
675

                            
                        
676
        if (prevId == dummyId && nextId == dummyId) {
677
            // No hint - descend list starting from head
678
            return _descendList(_NICR, data.head);
679
        } else if (prevId == dummyId) {
680
            // No `prevId` for hint - ascend list starting from `nextId`
681
            return _ascendList(_NICR, nextId);
682
        } else if (nextId == dummyId) {
683
            // No `nextId` for hint - descend list starting from `prevId`
684
            return _descendList(_NICR, prevId);
685
        } else {
686
            // Descend list starting from `prevId`
687
            return _descendList(_NICR, prevId);
688
        }
689
    }
690

                            
                        
691
    // --- 'require' functions ---
692

                            
                        
693
    /// @dev Asserts that the caller of the function is the CdpManager
694
    function _requireCallerIsCdpManager() internal view {
695
        require(msg.sender == address(cdpManager), "SortedCdps: Caller is not the CdpManager");
696
    }
697

                            
                        
698
    /// @dev Asserts that the caller of the function is either the BorrowerOperations contract or the CdpManager
699
    function _requireCallerIsBOorCdpM() internal view {
700
        require(
701
            msg.sender == borrowerOperationsAddress || msg.sender == address(cdpManager),
702
            "SortedCdps: Caller is neither BO nor CdpM"
703
        );
704
    }
705
}
706

                            
                        

Lines covered: 36 / 41 (87.8%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IPriceFeed.sol";
6
import "./Interfaces/ICdpManager.sol";
7
import "./Interfaces/ISortedCdps.sol";
8
import "./Interfaces/ICdpManagerData.sol";
9
import "./Dependencies/LiquityBase.sol";
10

                            
                        
11
/// @notice Helper to turn a sequence into CDP id array for batch liquidation
12
contract SyncedLiquidationSequencer is LiquityBase {
13
    ICdpManager public immutable cdpManager;
14
    ISortedCdps public immutable sortedCdps;
15

                            
                        
16
    constructor(
17
        address _cdpManagerAddress,
18
        address _sortedCdpsAddress,
19
        address _priceFeedAddress,
20
        address _activePoolAddress,
21
        address _collateralAddress
22
    ) LiquityBase(_activePoolAddress, _priceFeedAddress, _collateralAddress) {
23
        cdpManager = ICdpManager(_cdpManagerAddress);
24
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
25
    }
26

                            
                        
27
    /// @dev Get first N batch of liquidatable Cdps at current price
28
    /// @dev Non-view function that updates and returns live price at execution time
29
    function sequenceLiqToBatchLiq(uint256 _n) external returns (bytes32[] memory _array) {
30
        uint256 _price = priceFeed.fetchPrice();
31
        return sequenceLiqToBatchLiqWithPrice(_n, _price);
32
    }
33

                            
                        
34
    /// @dev Get first N batch of liquidatable Cdps at specified price
35
    function sequenceLiqToBatchLiqWithPrice(
36
        uint256 _n,
37
        uint256 _price
38
    ) public view returns (bytes32[] memory _array) {
39
        (uint256 _TCR, , ) = _getTCRWithSystemDebtAndCollShares(_price);
40
        bool _recoveryModeAtStart = _TCR < CCR ? true : false;
41
        return _sequenceLiqToBatchLiq(_n, _recoveryModeAtStart, _price);
42
    }
43

                            
                        
44
    // return CdpId array (in NICR-decreasing order same as SortedCdps)
45
    // including the last N CDPs in sortedCdps for batch liquidation
46
    function _sequenceLiqToBatchLiq(
47
        uint256 _n,
48
        bool _recoveryModeAtStart,
49
        uint256 _price
50
    ) internal view returns (bytes32[] memory _array) {
51
        if (_n > 0) {
52
            bytes32 _last = sortedCdps.getLast();
53
            bytes32 _first = sortedCdps.getFirst();
54
            bytes32 _cdpId = _last;
55

                            
                        
56
            uint256 _TCR = cdpManager.getSyncedTCR(_price);
57

                            
                        
58
            // get count of liquidatable CDPs
59
            uint256 _cnt;
60
            for (uint256 i = 0; i < _n && _cdpId != _first; ++i) {
61
                uint256 _icr = cdpManager.getSyncedICR(_cdpId, _price); /// @audit This is view ICR and not real ICR
62
                uint256 _cdpStatus = cdpManager.getCdpStatus(_cdpId);
63
                bool _liquidatable = _canLiquidateInCurrentMode(_recoveryModeAtStart, _icr, _TCR);
64
                if (_liquidatable && _cdpStatus == 1) {
65
                    _cnt += 1;
66
                }
67
                _cdpId = sortedCdps.getPrev(_cdpId);
68
            }
69

                            
                        
70
            // retrieve liquidatable CDPs
71
            _array = new bytes32[](_cnt);
72
            _cdpId = _last;
73
            uint256 _j;
74
            for (uint256 i = 0; i < _n && _cdpId != _first; ++i) {
75
                uint256 _icr = cdpManager.getSyncedICR(_cdpId, _price);
76
                uint256 _cdpStatus = cdpManager.getCdpStatus(_cdpId);
77
                bool _liquidatable = _canLiquidateInCurrentMode(_recoveryModeAtStart, _icr, _TCR);
78
                if (_liquidatable && _cdpStatus == 1) {
79
                    // 1 = ICdpManagerData.Status.active
80
                    _array[_cnt - _j - 1] = _cdpId;
81
                    _j += 1;
82
                }
83
                _cdpId = sortedCdps.getPrev(_cdpId);
84
            }
85
            require(_j == _cnt, "LiquidationLibrary: wrong sequence conversion!");
86
        }
87
    }
88

                            
                        
89
    function _canLiquidateInCurrentMode(
90
        bool _recovery,
91
        uint256 _icr,
92
        uint256 _TCR
93
    ) internal view returns (bool) {
94
        bool _liquidatable = _recovery ? (_icr < MCR || _icr < _TCR) : _icr < MCR;
95

                            
                        
96
        return _liquidatable;
97
    }
98
}
99

                            
                        

Lines covered: 6 / 6 (100.0%)

1
// SPDX-License-Identifier: UNLICENSED
2
pragma solidity 0.8.17;
3

                            
                        
4
import {WETH9} from "./WETH9.sol";
5
import {BorrowerOperations} from "../BorrowerOperations.sol";
6
import {PriceFeedTestnet} from "./testnet/PriceFeedTestnet.sol";
7
import {SortedCdps} from "../SortedCdps.sol";
8
import {CdpManager} from "../CdpManager.sol";
9
import {LiquidationLibrary} from "../LiquidationLibrary.sol";
10
import {LiquidationSequencer} from "../LiquidationSequencer.sol";
11
import {SyncedLiquidationSequencer} from "../SyncedLiquidationSequencer.sol";
12
import {ActivePool} from "../ActivePool.sol";
13
import {HintHelpers} from "../HintHelpers.sol";
14
import {FeeRecipient} from "../FeeRecipient.sol";
15
import {EBTCToken} from "../EBTCToken.sol";
16
import {CollSurplusPool} from "../CollSurplusPool.sol";
17
import {FunctionCaller} from "./FunctionCaller.sol";
18
import {CollateralTokenTester} from "./CollateralTokenTester.sol";
19
import {Governor} from "../Governor.sol";
20
import {EBTCDeployer} from "../EBTCDeployer.sol";
21
import {Actor} from "./invariants/Actor.sol";
22
import {CRLens} from "../CRLens.sol";
23

                            
                        
24
abstract contract BaseStorageVariables {
25
    PriceFeedTestnet internal priceFeedMock;
26
    SortedCdps internal sortedCdps;
27
    CdpManager internal cdpManager;
28
    WETH9 internal weth;
29
    ActivePool internal activePool;
30
    CollSurplusPool internal collSurplusPool;
31
    FunctionCaller internal functionCaller;
32
    BorrowerOperations internal borrowerOperations;
33
    HintHelpers internal hintHelpers;
34
    EBTCToken internal eBTCToken;
35
    CollateralTokenTester internal collateral;
36
    Governor internal authority;
37
    LiquidationLibrary internal liqudationLibrary;
38
    LiquidationSequencer internal liquidationSequencer;
39
    SyncedLiquidationSequencer internal syncedLiquidationSequencer;
40
    EBTCDeployer internal ebtcDeployer;
41
    address internal defaultGovernance;
42

                            
                        
43
    // LQTY Stuff
44
    FeeRecipient internal feeRecipient;
45

                            
                        
46
    mapping(address => Actor) internal actors;
47
    Actor internal actor;
48

                            
                        
49
    CRLens internal crLens;
50

                            
                        
51
    uint internal constant NUMBER_OF_ACTORS = 3;
52
    uint internal constant INITIAL_ETH_BALANCE = 1e24;
53
    uint internal constant INITIAL_COLL_BALANCE = 1e21;
54

                            
                        
55
    uint internal constant diff_tolerance = 0.000000000002e18; //compared to 1e18
56
    uint internal constant MAX_PRICE_CHANGE_PERCENT = 1.05e18; //compared to 1e18
57
    uint internal constant MAX_REBASE_PERCENT = 1.1e18; //compared to 1e18
58
    uint internal constant MAX_FLASHLOAN_ACTIONS = 4;
59
}
60

                            
                        

Lines covered: 56 / 113 (49.6%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
import "../Dependencies/ICollateralToken.sol";
5
import "../Dependencies/ICollateralTokenOracle.sol";
6
import "../Dependencies/Ownable.sol";
7

                            
                        
8
interface IEbtcInternalPool {
9
    function increaseSystemCollShares(uint256 _value) external;
10
}
11

                            
                        
12
// based on WETH9 contract
13
contract CollateralTokenTester is ICollateralToken, ICollateralTokenOracle, Ownable {
14
    string public override name = "Collateral Token Tester in eBTC";
15
    string public override symbol = "CollTester";
16
    uint8 public override decimals = 18;
17

                            
                        
18
    event Transfer(address indexed src, address indexed dst, uint256 wad, uint256 _share);
19
    event Deposit(address indexed dst, uint256 wad, uint256 _share);
20
    event Withdrawal(address indexed src, uint256 wad, uint256 _share);
21
    event UncappedMinterAdded(address indexed account);
22
    event UncappedMinterRemoved(address indexed account);
23
    event MintCapSet(uint256 indexed newCap);
24
    event MintCooldownSet(uint256 indexed newCooldown);
25

                            
                        
26
    mapping(address => uint256) private balances;
27
    mapping(address => mapping(address => uint256)) public override allowance;
28
    mapping(address => bool) public isUncappedMinter;
29
    mapping(address => uint256) public lastMintTime;
30

                            
                        
31
    // Faucet capped at 10 Collateral tokens per day
32
    uint256 public mintCap = 10e18;
33
    uint256 public mintCooldown = 60 * 60 * 24;
34

                            
                        
35
    uint256 private _ethPerShare = 1e18;
36
    uint256 private _totalBalance;
37

                            
                        
38
    uint256 private epochsPerFrame = 225;
39
    uint256 private slotsPerEpoch = 32;
40
    uint256 private secondsPerSlot = 12;
41

                            
                        
42
    receive() external payable {
43
        deposit();
44
    }
45

                            
                        
46
    function deposit() public payable {
47
        uint256 _share = getSharesByPooledEth(msg.value);
48
        balances[msg.sender] += _share;
49
        _totalBalance += _share;
50
        emit Deposit(msg.sender, msg.value, _share);
51
    }
52

                            
                        
53
    /// @dev Deposit collateral without ether for testing purposes
54
    function forceDeposit(uint256 ethToDeposit) external {
55
        if (!isUncappedMinter[msg.sender]) {
56
            require(ethToDeposit <= mintCap, "CollTester: Above mint cap");
57
            require(
58
                lastMintTime[msg.sender] == 0 ||
59
                    lastMintTime[msg.sender] + mintCooldown < block.timestamp,
60
                "CollTester: Cooldown period not completed"
61
            );
62
            lastMintTime[msg.sender] = block.timestamp;
63
        }
64
        uint256 _share = getSharesByPooledEth(ethToDeposit);
65
        balances[msg.sender] += _share;
66
        _totalBalance += _share;
67
        emit Deposit(msg.sender, ethToDeposit, _share);
68
    }
69

                            
                        
70
    function withdraw(uint256 wad) public {
71
        uint256 _share = getSharesByPooledEth(wad);
72
        require(balances[msg.sender] >= _share);
73
        balances[msg.sender] -= _share;
74
        _totalBalance -= _share;
75
        payable(msg.sender).transfer(wad);
76
        emit Withdrawal(msg.sender, wad, _share);
77
    }
78

                            
                        
79
    function totalSupply() public view override returns (uint) {
80
        uint _tmp = _mul(_ethPerShare, _totalBalance);
81
        return _div(_tmp, 1e18);
82
    }
83

                            
                        
84
    // Permissioned functions
85
    function addUncappedMinter(address account) external onlyOwner {
86
        isUncappedMinter[account] = true;
87
        emit UncappedMinterAdded(account);
88
    }
89

                            
                        
90
    function removeUncappedMinter(address account) external onlyOwner {
91
        isUncappedMinter[account] = false;
92
        emit UncappedMinterRemoved(account);
93
    }
94

                            
                        
95
    function setMintCap(uint256 newCap) external onlyOwner {
96
        mintCap = newCap;
97
        emit MintCapSet(newCap);
98
    }
99

                            
                        
100
    function setMintCooldown(uint256 newCooldown) external onlyOwner {
101
        mintCooldown = newCooldown;
102
        emit MintCooldownSet(newCooldown);
103
    }
104

                            
                        
105
    // helper to set allowance in test
106
    function nonStandardSetApproval(
107
        address owner,
108
        address guy,
109
        uint256 wad
110
    ) external returns (bool) {
111
        allowance[owner][guy] = wad;
112
        emit Approval(owner, guy, wad);
113
        return true;
114
    }
115

                            
                        
116
    function approve(address guy, uint256 wad) public override returns (bool) {
117
        allowance[msg.sender][guy] = wad;
118
        emit Approval(msg.sender, guy, wad);
119
        return true;
120
    }
121

                            
                        
122
    function transfer(address dst, uint256 wad) public override returns (bool) {
123
        return transferFrom(msg.sender, dst, wad);
124
    }
125

                            
                        
126
    function transferFrom(address src, address dst, uint256 wad) public override returns (bool) {
127
        uint256 _share = getSharesByPooledEth(wad);
128
        require(balances[src] >= _share, "ERC20: transfer amount exceeds balance");
129

                            
                        
130
        if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
131
            require(allowance[src][msg.sender] >= wad);
132
            allowance[src][msg.sender] -= wad;
133
        }
134

                            
                        
135
        balances[src] -= _share;
136
        balances[dst] += _share;
137

                            
                        
138
        emit Transfer(src, dst, wad, _share);
139

                            
                        
140
        return true;
141
    }
142

                            
                        
143
    // tests should adjust the ratio by this function
144
    function setEthPerShare(uint256 _ePerS) external {
145
        _ethPerShare = _ePerS;
146
    }
147

                            
                        
148
    function getEthPerShare() external view returns (uint256) {
149
        return _ethPerShare;
150
    }
151

                            
                        
152
    function getSharesByPooledEth(uint256 _ethAmount) public view override returns (uint256) {
153
        uint256 _tmp = _mul(1e18, _ethAmount);
154
        return _div(_tmp, _ethPerShare);
155
    }
156

                            
                        
157
    function getPooledEthByShares(uint256 _sharesAmount) public view override returns (uint256) {
158
        uint256 _tmp = _mul(_ethPerShare, _sharesAmount);
159
        return _div(_tmp, 1e18);
160
    }
161

                            
                        
162
    function transferShares(
163
        address _recipient,
164
        uint256 _sharesAmount
165
    ) public override returns (uint256) {
166
        uint256 _tknAmt = getPooledEthByShares(_sharesAmount);
167

                            
                        
168
        // NOTE: Changed here to transfer underlying shares without rounding
169
        balances[msg.sender] -= _sharesAmount;
170
        balances[_recipient] += _sharesAmount;
171

                            
                        
172
        emit Transfer(msg.sender, _recipient, _tknAmt, _sharesAmount);
173

                            
                        
174
        return _tknAmt;
175
    }
176

                            
                        
177
    function sharesOf(address _account) public view override returns (uint256) {
178
        return balances[_account];
179
    }
180

                            
                        
181
    function getOracle() external view override returns (address) {
182
        return address(this);
183
    }
184

                            
                        
185
    function getBeaconSpec() public view override returns (uint64, uint64, uint64, uint64) {
186
        return (
187
            uint64(epochsPerFrame),
188
            uint64(slotsPerEpoch),
189
            uint64(secondsPerSlot),
190
            uint64(block.timestamp)
191
        );
192
    }
193

                            
                        
194
    function setBeaconSpec(
195
        uint64 _epochsPerFrame,
196
        uint64 _slotsPerEpoch,
197
        uint64 _secondsPerSlot
198
    ) external {
199
        epochsPerFrame = _epochsPerFrame;
200
        slotsPerEpoch = _slotsPerEpoch;
201
        secondsPerSlot = _secondsPerSlot;
202
    }
203

                            
                        
204
    function decreaseAllowance(
205
        address spender,
206
        uint256 subtractedValue
207
    ) external override returns (bool) {
208
        approve(spender, allowance[msg.sender][spender] - subtractedValue);
209
        return true;
210
    }
211

                            
                        
212
    function balanceOf(address _usr) external view override returns (uint256) {
213
        uint256 _tmp = _mul(_ethPerShare, balances[_usr]);
214
        return _div(_tmp, 1e18);
215
    }
216

                            
                        
217
    function increaseAllowance(
218
        address spender,
219
        uint256 addedValue
220
    ) external override returns (bool) {
221
        approve(spender, allowance[msg.sender][spender] + addedValue);
222
        return true;
223
    }
224

                            
                        
225
    // internal helper functions
226
    function _mul(uint256 a, uint256 b) internal pure returns (uint256) {
227
        if (a == 0) {
228
            return 0;
229
        }
230
        uint256 c = a * b;
231
        require(c / a == b, "SafeMath: multiplication overflow");
232
        return c;
233
    }
234

                            
                        
235
    function _div(uint256 a, uint256 b) internal pure returns (uint256) {
236
        require(b > 0, "SafeMath: zero denominator");
237
        uint256 c = a / b;
238
        return c;
239
    }
240

                            
                        
241
    // dummy test purpose
242
    function feeRecipientAddress() external view returns (address) {
243
        return address(this);
244
    }
245

                            
                        
246
    function authority() external view returns (address) {
247
        return address(this);
248
    }
249
}
250

                            
                        

Lines covered: 2 / 15 (13.3%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../EBTCToken.sol";
6

                            
                        
7
contract EBTCTokenTester is EBTCToken {
8
    bytes32 private immutable _PERMIT_TYPEHASH =
9
        0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
10

                            
                        
11
    constructor(
12
        address _cdpManagerAddress,
13
        address _borrowerOperationsAddress,
14
        address _authorityAddress
15
    ) EBTCToken(_cdpManagerAddress, _borrowerOperationsAddress, _authorityAddress) {}
16

                            
                        
17
    function unprotectedMint(address _account, uint256 _amount) external {
18
        // No check on caller here
19

                            
                        
20
        _mint(_account, _amount);
21
    }
22

                            
                        
23
    function unprotectedBurn(address _account, uint256 _amount) external {
24
        // No check on caller here
25

                            
                        
26
        _burn(_account, _amount);
27
    }
28

                            
                        
29
    function unprotectedSendToPool(address _sender, address _poolAddress, uint256 _amount) external {
30
        // No check on caller here
31

                            
                        
32
        _transfer(_sender, _poolAddress, _amount);
33
    }
34

                            
                        
35
    function unprotectedReturnFromPool(
36
        address _poolAddress,
37
        address _receiver,
38
        uint256 _amount
39
    ) external {
40
        // No check on caller here
41

                            
                        
42
        _transfer(_poolAddress, _receiver, _amount);
43
    }
44

                            
                        
45
    function callInternalApprove(address owner, address spender, uint256 amount) external {
46
        _approve(owner, spender, amount);
47
    }
48

                            
                        
49
    function getChainId() external view returns (uint256 chainID) {
50
        //return _chainID(); // it’s private
51
        assembly {
52
            chainID := chainid()
53
        }
54
    }
55

                            
                        
56
    function getDigest(
57
        address owner,
58
        address spender,
59
        uint256 amount,
60
        uint256 nonce,
61
        uint256 deadline
62
    ) external view returns (bytes32) {
63
        return
64
            keccak256(
65
                abi.encodePacked(
66
                    uint16(0x1901),
67
                    domainSeparator(),
68
                    keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, nonce, deadline))
69
                )
70
            );
71
    }
72

                            
                        
73
    function recoverAddress(
74
        bytes32 digest,
75
        uint8 v,
76
        bytes32 r,
77
        bytes32 s
78
    ) external pure returns (address) {
79
        return ecrecover(digest, v, r, s);
80
    }
81
}
82

                            
                        

Lines covered: 0 / 14 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../Interfaces/ICdpManager.sol";
6
import "../Interfaces/ISortedCdps.sol";
7
import "../Interfaces/IPriceFeed.sol";
8
import "../Dependencies/LiquityMath.sol";
9

                            
                        
10
/* Wrapper contract - used for calculating gas of read-only and internal functions. 
11
Not part of the Liquity application. */
12
contract FunctionCaller {
13
    ICdpManager cdpManager;
14
    address public cdpManagerAddress;
15

                            
                        
16
    ISortedCdps sortedCdps;
17
    address public sortedCdpsAddress;
18

                            
                        
19
    IPriceFeed priceFeed;
20
    address public priceFeedAddress;
21

                            
                        
22
    // --- Dependency setters ---
23

                            
                        
24
    function setCdpManagerAddress(address _cdpManagerAddress) external {
25
        cdpManagerAddress = _cdpManagerAddress;
26
        cdpManager = ICdpManager(_cdpManagerAddress);
27
    }
28

                            
                        
29
    function setSortedCdpsAddress(address _sortedCdpsAddress) external {
30
        cdpManagerAddress = _sortedCdpsAddress;
31
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
32
    }
33

                            
                        
34
    function setPriceFeedAddress(address _priceFeedAddress) external {
35
        priceFeedAddress = _priceFeedAddress;
36
        priceFeed = IPriceFeed(_priceFeedAddress);
37
    }
38

                            
                        
39
    // --- Non-view wrapper functions used for calculating gas ---
40

                            
                        
41
    function cdpManager_getICR(bytes32 _cdpId, uint256 _price) external view returns (uint256) {
42
        return cdpManager.getICR(_cdpId, _price);
43
    }
44

                            
                        
45
    function sortedCdps_findInsertPosition(
46
        uint256 _NICR,
47
        bytes32 _prevId,
48
        bytes32 _nextId
49
    ) external view returns (bytes32, bytes32) {
50
        return sortedCdps.findInsertPosition(_NICR, _prevId, _nextId);
51
    }
52
}
53

                            
                        

Lines covered: 0 / 91 (0.0%)

1
// https://github.com/one-hundred-proof/kyberswap-exploit/blob/main/lib/helpers/Pretty.sol
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
library Strings {
6
    function concat(
7
        string memory _base,
8
        string memory _value
9
    ) internal pure returns (string memory) {
10
        bytes memory _baseBytes = bytes(_base);
11
        bytes memory _valueBytes = bytes(_value);
12

                            
                        
13
        string memory _tmpValue = new string(_baseBytes.length + _valueBytes.length);
14
        bytes memory _newValue = bytes(_tmpValue);
15

                            
                        
16
        uint i;
17
        uint j;
18

                            
                        
19
        for (i = 0; i < _baseBytes.length; i++) {
20
            _newValue[j++] = _baseBytes[i];
21
        }
22

                            
                        
23
        for (i = 0; i < _valueBytes.length; i++) {
24
            _newValue[j++] = _valueBytes[i];
25
        }
26

                            
                        
27
        return string(_newValue);
28
    }
29
}
30

                            
                        
31
library Pretty {
32
    uint8 constant DEFAULT_DECIMALS = 18;
33

                            
                        
34
    function toBitString(uint256 n) external pure returns (string memory) {
35
        return uintToBitString(n, 256);
36
    }
37

                            
                        
38
    function toBitString(uint256 n, uint8 decimals) external pure returns (string memory) {
39
        return uintToBitString(n, decimals);
40
    }
41

                            
                        
42
    function pretty(uint256 n) external pure returns (string memory) {
43
        return
44
            n == type(uint256).max ? "type(uint256).max" : n == type(uint128).max
45
                ? "type(uint128).max"
46
                : _pretty(n, DEFAULT_DECIMALS);
47
    }
48

                            
                        
49
    function pretty(bool value) external pure returns (string memory) {
50
        return value ? "true" : "false";
51
    }
52

                            
                        
53
    function pretty(uint256 n, uint8 decimals) external pure returns (string memory) {
54
        return _pretty(n, decimals);
55
    }
56

                            
                        
57
    function pretty(int256 n) external pure returns (string memory) {
58
        return _prettyInt(n, DEFAULT_DECIMALS);
59
    }
60

                            
                        
61
    function pretty(int256 n, uint8 decimals) external pure returns (string memory) {
62
        return _prettyInt(n, decimals);
63
    }
64

                            
                        
65
    function _pretty(uint256 n, uint8 decimals) internal pure returns (string memory) {
66
        bool pastDecimals = decimals == 0;
67
        uint256 place = 0;
68
        uint256 r; // remainder
69
        string memory s = "";
70

                            
                        
71
        while (n != 0) {
72
            r = n % 10;
73
            n /= 10;
74
            place++;
75
            s = Strings.concat(toDigit(r), s);
76
            if (pastDecimals && place % 3 == 0 && n != 0) {
77
                s = Strings.concat("_", s);
78
            }
79
            if (!pastDecimals && place == decimals) {
80
                pastDecimals = true;
81
                place = 0;
82
                s = Strings.concat("_", s);
83
            }
84
        }
85
        if (pastDecimals && place == 0) {
86
            s = Strings.concat("0", s);
87
        }
88
        if (!pastDecimals) {
89
            uint256 i;
90
            uint256 upper = (decimals >= place ? decimals - place : 0);
91
            for (i = 0; i < upper; ++i) {
92
                s = Strings.concat("0", s);
93
            }
94
            s = Strings.concat("0_", s);
95
        }
96
        return s;
97
    }
98

                            
                        
99
    function _prettyInt(int256 n, uint8 decimals) internal pure returns (string memory) {
100
        bool isNegative = n < 0;
101
        string memory s = "";
102
        if (isNegative) {
103
            s = "-";
104
        }
105
        return Strings.concat(s, _pretty(uint256(isNegative ? -n : n), decimals));
106
    }
107

                            
                        
108
    function toDigit(uint256 n) internal pure returns (string memory) {
109
        if (n == 0) {
110
            return "0";
111
        } else if (n == 1) {
112
            return "1";
113
        } else if (n == 2) {
114
            return "2";
115
        } else if (n == 3) {
116
            return "3";
117
        } else if (n == 4) {
118
            return "4";
119
        } else if (n == 5) {
120
            return "5";
121
        } else if (n == 6) {
122
            return "6";
123
        } else if (n == 7) {
124
            return "7";
125
        } else if (n == 8) {
126
            return "8";
127
        } else if (n == 9) {
128
            return "9";
129
        } else {
130
            revert("Not in range 0 to 10");
131
        }
132
    }
133

                            
                        
134
    function uintToBitString(uint256 n, uint16 bits) internal pure returns (string memory) {
135
        string memory s = "";
136
        for (uint256 i; i < bits; i++) {
137
            if (n % 2 == 0) {
138
                s = Strings.concat("0", s);
139
            } else {
140
                s = Strings.concat("1", s);
141
            }
142
            n = n / 2;
143
        }
144
        return s;
145
    }
146
}
147

                            
                        

Lines covered: 0 / 29 (0.0%)

1
/**
2
 *Submitted for verification at Etherscan.io on 2017-12-12
3
 */
4

                            
                        
5
// Copyright (C) 2015, 2016, 2017 Dapphub
6

                            
                        
7
// This program is free software: you can redistribute it and/or modify
8
// it under the terms of the GNU General Public License as published by
9
// the Free Software Foundation, either version 3 of the License, or
10
// (at your option) any later version.
11

                            
                        
12
// This program is distributed in the hope that it will be useful,
13
// but WITHOUT ANY WARRANTY; without even the implied warranty of
14
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
// GNU General Public License for more details.
16

                            
                        
17
// You should have received a copy of the GNU General Public License
18
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
19

                            
                        
20
pragma solidity 0.8.17;
21

                            
                        
22
contract WETH9 {
23
    string public name = "Wrapped Ether";
24
    string public symbol = "WETH";
25
    uint8 public decimals = 18;
26

                            
                        
27
    event Approval(address indexed src, address indexed guy, uint256 wad);
28
    event Transfer(address indexed src, address indexed dst, uint256 wad);
29
    event Deposit(address indexed dst, uint256 wad);
30
    event Withdrawal(address indexed src, uint256 wad);
31

                            
                        
32
    mapping(address => uint256) public balanceOf;
33
    mapping(address => mapping(address => uint256)) public allowance;
34

                            
                        
35
    function receive() public payable {
36
        deposit();
37
    }
38

                            
                        
39
    function deposit() public payable {
40
        balanceOf[msg.sender] += msg.value;
41
        emit Deposit(msg.sender, msg.value);
42
    }
43

                            
                        
44
    function withdraw(uint256 wad) public {
45
        require(balanceOf[msg.sender] >= wad);
46
        balanceOf[msg.sender] -= wad;
47
        payable(msg.sender).transfer(wad);
48
        emit Withdrawal(msg.sender, wad);
49
    }
50

                            
                        
51
    function totalSupply() public view returns (uint256) {
52
        return address(this).balance;
53
    }
54

                            
                        
55
    function approve(address guy, uint256 wad) public returns (bool) {
56
        allowance[msg.sender][guy] = wad;
57
        emit Approval(msg.sender, guy, wad);
58
        return true;
59
    }
60

                            
                        
61
    function transfer(address dst, uint256 wad) public virtual returns (bool) {
62
        return transferFrom(msg.sender, dst, wad);
63
    }
64

                            
                        
65
    function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
66
        require(balanceOf[src] >= wad);
67

                            
                        
68
        if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
69
            require(allowance[src][msg.sender] >= wad);
70
            allowance[src][msg.sender] -= wad;
71
        }
72

                            
                        
73
        balanceOf[src] -= wad;
74
        balanceOf[dst] += wad;
75

                            
                        
76
        emit Transfer(src, dst, wad);
77

                            
                        
78
        return true;
79
    }
80
}
81

                            
                        
82
/*
83
                    GNU GENERAL PUBLIC LICENSE
84
                       Version 3, 29 June 2007
85

                            
                        
86
 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
87
 Everyone is permitted to copy and distribute verbatim copies
88
 of this license document, but changing it is not allowed.
89

                            
                        
90
                            Preamble
91

                            
                        
92
  The GNU General Public License is a free, copyleft license for
93
software and other kinds of works.
94

                            
                        
95
  The licenses for most software and other practical works are designed
96
to take away your freedom to share and change the works.  By contrast,
97
the GNU General Public License is intended to guarantee your freedom to
98
share and change all versions of a program--to make sure it remains free
99
software for all its users.  We, the Free Software Foundation, use the
100
GNU General Public License for most of our software; it applies also to
101
any other work released this way by its authors.  You can apply it to
102
your programs, too.
103

                            
                        
104
  When we speak of free software, we are referring to freedom, not
105
price.  Our General Public Licenses are designed to make sure that you
106
have the freedom to distribute copies of free software (and charge for
107
them if you wish), that you receive source code or can get it if you
108
want it, that you can change the software or use pieces of it in new
109
free programs, and that you know you can do these things.
110

                            
                        
111
  To protect your rights, we need to prevent others from denying you
112
these rights or asking you to surrender the rights.  Therefore, you have
113
certain responsibilities if you distribute copies of the software, or if
114
you modify it: responsibilities to respect the freedom of others.
115

                            
                        
116
  For example, if you distribute copies of such a program, whether
117
gratis or for a fee, you must pass on to the recipients the same
118
freedoms that you received.  You must make sure that they, too, receive
119
or can get the source code.  And you must show them these terms so they
120
know their rights.
121

                            
                        
122
  Developers that use the GNU GPL protect your rights with two steps:
123
(1) assert copyright on the software, and (2) offer you this License
124
giving you legal permission to copy, distribute and/or modify it.
125

                            
                        
126
  For the developers' and authors' protection, the GPL clearly explains
127
that there is no warranty for this free software.  For both users' and
128
authors' sake, the GPL requires that modified versions be marked as
129
changed, so that their problems will not be attributed erroneously to
130
authors of previous versions.
131

                            
                        
132
  Some devices are designed to deny users access to install or run
133
modified versions of the software inside them, although the manufacturer
134
can do so.  This is fundamentally incompatible with the aim of
135
protecting users' freedom to change the software.  The systematic
136
pattern of such abuse occurs in the area of products for individuals to
137
use, which is precisely where it is most unacceptable.  Therefore, we
138
have designed this version of the GPL to prohibit the practice for those
139
products.  If such problems arise substantially in other domains, we
140
stand ready to extend this provision to those domains in future versions
141
of the GPL, as needed to protect the freedom of users.
142

                            
                        
143
  Finally, every program is threatened constantly by software patents.
144
States should not allow patents to restrict development and use of
145
software on general-purpose computers, but in those that do, we wish to
146
avoid the special danger that patents applied to a free program could
147
make it effectively proprietary.  To prevent this, the GPL assures that
148
patents cannot be used to render the program non-free.
149

                            
                        
150
  The precise terms and conditions for copying, distribution and
151
modification follow.
152

                            
                        
153
                       TERMS AND CONDITIONS
154

                            
                        
155
  0. Definitions.
156

                            
                        
157
  "This License" refers to version 3 of the GNU General Public License.
158

                            
                        
159
  "Copyright" also means copyright-like laws that apply to other kinds of
160
works, such as semiconductor masks.
161

                            
                        
162
  "The Program" refers to any copyrightable work licensed under this
163
License.  Each licensee is addressed as "you".  "Licensees" and
164
"recipients" may be individuals or organizations.
165

                            
                        
166
  To "modify" a work means to copy from or adapt all or part of the work
167
in a fashion requiring copyright permission, other than the making of an
168
exact copy.  The resulting work is called a "modified version" of the
169
earlier work or a work "based on" the earlier work.
170

                            
                        
171
  A "covered work" means either the unmodified Program or a work based
172
on the Program.
173

                            
                        
174
  To "propagate" a work means to do anything with it that, without
175
permission, would make you directly or secondarily liable for
176
infringement under applicable copyright law, except executing it on a
177
computer or modifying a private copy.  Propagation includes copying,
178
distribution (with or without modification), making available to the
179
public, and in some countries other activities as well.
180

                            
                        
181
  To "convey" a work means any kind of propagation that enables other
182
parties to make or receive copies.  Mere interaction with a user through
183
a computer network, with no transfer of a copy, is not conveying.
184

                            
                        
185
  An interactive user interface displays "Appropriate Legal Notices"
186
to the extent that it includes a convenient and prominently visible
187
feature that (1) displays an appropriate copyright notice, and (2)
188
tells the user that there is no warranty for the work (except to the
189
extent that warranties are provided), that licensees may convey the
190
work under this License, and how to view a copy of this License.  If
191
the interface presents a list of user commands or options, such as a
192
menu, a prominent item in the list meets this criterion.
193

                            
                        
194
  1. Source Code.
195

                            
                        
196
  The "source code" for a work means the preferred form of the work
197
for making modifications to it.  "Object code" means any non-source
198
form of a work.
199

                            
                        
200
  A "Standard Interface" means an interface that either is an official
201
standard defined by a recognized standards body, or, in the case of
202
interfaces specified for a particular programming language, one that
203
is widely used among developers working in that language.
204

                            
                        
205
  The "System Libraries" of an executable work include anything, other
206
than the work as a whole, that (a) is included in the normal form of
207
packaging a Major Component, but which is not part of that Major
208
Component, and (b) serves only to enable use of the work with that
209
Major Component, or to implement a Standard Interface for which an
210
implementation is available to the public in source code form.  A
211
"Major Component", in this context, means a major essential component
212
(kernel, window system, and so on) of the specific operating system
213
(if any) on which the executable work runs, or a compiler used to
214
produce the work, or an object code interpreter used to run it.
215

                            
                        
216
  The "Corresponding Source" for a work in object code form means all
217
the source code needed to generate, install, and (for an executable
218
work) run the object code and to modify the work, including scripts to
219
control those activities.  However, it does not include the work's
220
System Libraries, or general-purpose tools or generally available free
221
programs which are used unmodified in performing those activities but
222
which are not part of the work.  For example, Corresponding Source
223
includes interface definition files associated with source files for
224
the work, and the source code for shared libraries and dynamically
225
linked subprograms that the work is specifically designed to require,
226
such as by intimate data communication or control flow between those
227
subprograms and other parts of the work.
228

                            
                        
229
  The Corresponding Source need not include anything that users
230
can regenerate automatically from other parts of the Corresponding
231
Source.
232

                            
                        
233
  The Corresponding Source for a work in source code form is that
234
same work.
235

                            
                        
236
  2. Basic Permissions.
237

                            
                        
238
  All rights granted under this License are granted for the term of
239
copyright on the Program, and are irrevocable provided the stated
240
conditions are met.  This License explicitly affirms your unlimited
241
permission to run the unmodified Program.  The output from running a
242
covered work is covered by this License only if the output, given its
243
content, constitutes a covered work.  This License acknowledges your
244
rights of fair use or other equivalent, as provided by copyright law.
245

                            
                        
246
  You may make, run and propagate covered works that you do not
247
convey, without conditions so long as your license otherwise remains
248
in force.  You may convey covered works to others for the sole purpose
249
of having them make modifications exclusively for you, or provide you
250
with facilities for running those works, provided that you comply with
251
the terms of this License in conveying all material for which you do
252
not control copyright.  Those thus making or running the covered works
253
for you must do so exclusively on your behalf, under your direction
254
and control, on terms that prohibit them from making any copies of
255
your copyrighted material outside their relationship with you.
256

                            
                        
257
  Conveying under any other circumstances is permitted solely under
258
the conditions stated below.  Sublicensing is not allowed; section 10
259
makes it unnecessary.
260

                            
                        
261
  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
262

                            
                        
263
  No covered work shall be deemed part of an effective technological
264
measure under any applicable law fulfilling obligations under article
265
11 of the WIPO copyright treaty adopted on 20 December 1996, or
266
similar laws prohibiting or restricting circumvention of such
267
measures.
268

                            
                        
269
  When you convey a covered work, you waive any legal power to forbid
270
circumvention of technological measures to the extent such circumvention
271
is effected by exercising rights under this License with respect to
272
the covered work, and you disclaim any intention to limit operation or
273
modification of the work as a means of enforcing, against the work's
274
users, your or third parties' legal rights to forbid circumvention of
275
technological measures.
276

                            
                        
277
  4. Conveying Verbatim Copies.
278

                            
                        
279
  You may convey verbatim copies of the Program's source code as you
280
receive it, in any medium, provided that you conspicuously and
281
appropriately publish on each copy an appropriate copyright notice;
282
keep intact all notices stating that this License and any
283
non-permissive terms added in accord with section 7 apply to the code;
284
keep intact all notices of the absence of any warranty; and give all
285
recipients a copy of this License along with the Program.
286

                            
                        
287
  You may charge any price or no price for each copy that you convey,
288
and you may offer support or warranty protection for a fee.
289

                            
                        
290
  5. Conveying Modified Source Versions.
291

                            
                        
292
  You may convey a work based on the Program, or the modifications to
293
produce it from the Program, in the form of source code under the
294
terms of section 4, provided that you also meet all of these conditions:
295

                            
                        
296
    a) The work must carry prominent notices stating that you modified
297
    it, and giving a relevant date.
298

                            
                        
299
    b) The work must carry prominent notices stating that it is
300
    released under this License and any conditions added under section
301
    7.  This requirement modifies the requirement in section 4 to
302
    "keep intact all notices".
303

                            
                        
304
    c) You must license the entire work, as a whole, under this
305
    License to anyone who comes into possession of a copy.  This
306
    License will therefore apply, along with any applicable section 7
307
    additional terms, to the whole of the work, and all its parts,
308
    regardless of how they are packaged.  This License gives no
309
    permission to license the work in any other way, but it does not
310
    invalidate such permission if you have separately received it.
311

                            
                        
312
    d) If the work has interactive user interfaces, each must display
313
    Appropriate Legal Notices; however, if the Program has interactive
314
    interfaces that do not display Appropriate Legal Notices, your
315
    work need not make them do so.
316

                            
                        
317
  A compilation of a covered work with other separate and independent
318
works, which are not by their nature extensions of the covered work,
319
and which are not combined with it such as to form a larger program,
320
in or on a volume of a storage or distribution medium, is called an
321
"aggregate" if the compilation and its resulting copyright are not
322
used to limit the access or legal rights of the compilation's users
323
beyond what the individual works permit.  Inclusion of a covered work
324
in an aggregate does not cause this License to apply to the other
325
parts of the aggregate.
326

                            
                        
327
  6. Conveying Non-Source Forms.
328

                            
                        
329
  You may convey a covered work in object code form under the terms
330
of sections 4 and 5, provided that you also convey the
331
machine-readable Corresponding Source under the terms of this License,
332
in one of these ways:
333

                            
                        
334
    a) Convey the object code in, or embodied in, a physical product
335
    (including a physical distribution medium), accompanied by the
336
    Corresponding Source fixed on a durable physical medium
337
    customarily used for software interchange.
338

                            
                        
339
    b) Convey the object code in, or embodied in, a physical product
340
    (including a physical distribution medium), accompanied by a
341
    written offer, valid for at least three years and valid for as
342
    long as you offer spare parts or customer support for that product
343
    model, to give anyone who possesses the object code either (1) a
344
    copy of the Corresponding Source for all the software in the
345
    product that is covered by this License, on a durable physical
346
    medium customarily used for software interchange, for a price no
347
    more than your reasonable cost of physically performing this
348
    conveying of source, or (2) access to copy the
349
    Corresponding Source from a network server at no charge.
350

                            
                        
351
    c) Convey individual copies of the object code with a copy of the
352
    written offer to provide the Corresponding Source.  This
353
    alternative is allowed only occasionally and noncommercially, and
354
    only if you received the object code with such an offer, in accord
355
    with subsection 6b.
356

                            
                        
357
    d) Convey the object code by offering access from a designated
358
    place (gratis or for a charge), and offer equivalent access to the
359
    Corresponding Source in the same way through the same place at no
360
    further charge.  You need not require recipients to copy the
361
    Corresponding Source along with the object code.  If the place to
362
    copy the object code is a network server, the Corresponding Source
363
    may be on a different server (operated by you or a third party)
364
    that supports equivalent copying facilities, provided you maintain
365
    clear directions next to the object code saying where to find the
366
    Corresponding Source.  Regardless of what server hosts the
367
    Corresponding Source, you remain obligated to ensure that it is
368
    available for as long as needed to satisfy these requirements.
369

                            
                        
370
    e) Convey the object code using peer-to-peer transmission, provided
371
    you inform other peers where the object code and Corresponding
372
    Source of the work are being offered to the general public at no
373
    charge under subsection 6d.
374

                            
                        
375
  A separable portion of the object code, whose source code is excluded
376
from the Corresponding Source as a System Library, need not be
377
included in conveying the object code work.
378

                            
                        
379
  A "User Product" is either (1) a "consumer product", which means any
380
tangible personal property which is normally used for personal, family,
381
or household purposes, or (2) anything designed or sold for incorporation
382
into a dwelling.  In determining whether a product is a consumer product,
383
doubtful cases shall be resolved in favor of coverage.  For a particular
384
product received by a particular user, "normally used" refers to a
385
typical or common use of that class of product, regardless of the status
386
of the particular user or of the way in which the particular user
387
actually uses, or expects or is expected to use, the product.  A product
388
is a consumer product regardless of whether the product has substantial
389
commercial, industrial or non-consumer uses, unless such uses represent
390
the only significant mode of use of the product.
391

                            
                        
392
  "Installation Information" for a User Product means any methods,
393
procedures, authorization keys, or other information required to install
394
and execute modified versions of a covered work in that User Product from
395
a modified version of its Corresponding Source.  The information must
396
suffice to ensure that the continued functioning of the modified object
397
code is in no case prevented or interfered with solely because
398
modification has been made.
399

                            
                        
400
  If you convey an object code work under this section in, or with, or
401
specifically for use in, a User Product, and the conveying occurs as
402
part of a transaction in which the right of possession and use of the
403
User Product is transferred to the recipient in perpetuity or for a
404
fixed term (regardless of how the transaction is characterized), the
405
Corresponding Source conveyed under this section must be accompanied
406
by the Installation Information.  But this requirement does not apply
407
if neither you nor any third party retains the ability to install
408
modified object code on the User Product (for example, the work has
409
been installed in ROM).
410

                            
                        
411
  The requirement to provide Installation Information does not include a
412
requirement to continue to provide support service, warranty, or updates
413
for a work that has been modified or installed by the recipient, or for
414
the User Product in which it has been modified or installed.  Access to a
415
network may be denied when the modification itself materially and
416
adversely affects the operation of the network or violates the rules and
417
protocols for communication across the network.
418

                            
                        
419
  Corresponding Source conveyed, and Installation Information provided,
420
in accord with this section must be in a format that is publicly
421
documented (and with an implementation available to the public in
422
source code form), and must require no special password or key for
423
unpacking, reading or copying.
424

                            
                        
425
  7. Additional Terms.
426

                            
                        
427
  "Additional permissions" are terms that supplement the terms of this
428
License by making exceptions from one or more of its conditions.
429
Additional permissions that are applicable to the entire Program shall
430
be treated as though they were included in this License, to the extent
431
that they are valid under applicable law.  If additional permissions
432
apply only to part of the Program, that part may be used separately
433
under those permissions, but the entire Program remains governed by
434
this License without regard to the additional permissions.
435

                            
                        
436
  When you convey a copy of a covered work, you may at your option
437
remove any additional permissions from that copy, or from any part of
438
it.  (Additional permissions may be written to require their own
439
removal in certain cases when you modify the work.)  You may place
440
additional permissions on material, added by you to a covered work,
441
for which you have or can give appropriate copyright permission.
442

                            
                        
443
  Notwithstanding any other provision of this License, for material you
444
add to a covered work, you may (if authorized by the copyright holders of
445
that material) supplement the terms of this License with terms:
446

                            
                        
447
    a) Disclaiming warranty or limiting liability differently from the
448
    terms of sections 15 and 16 of this License; or
449

                            
                        
450
    b) Requiring preservation of specified reasonable legal notices or
451
    author attributions in that material or in the Appropriate Legal
452
    Notices displayed by works containing it; or
453

                            
                        
454
    c) Prohibiting misrepresentation of the origin of that material, or
455
    requiring that modified versions of such material be marked in
456
    reasonable ways as different from the original version; or
457

                            
                        
458
    d) Limiting the use for publicity purposes of names of licensors or
459
    authors of the material; or
460

                            
                        
461
    e) Declining to grant rights under trademark law for use of some
462
    trade names, trademarks, or service marks; or
463

                            
                        
464
    f) Requiring indemnification of licensors and authors of that
465
    material by anyone who conveys the material (or modified versions of
466
    it) with contractual assumptions of liability to the recipient, for
467
    any liability that these contractual assumptions directly impose on
468
    those licensors and authors.
469

                            
                        
470
  All other non-permissive additional terms are considered "further
471
restrictions" within the meaning of section 10.  If the Program as you
472
received it, or any part of it, contains a notice stating that it is
473
governed by this License along with a term that is a further
474
restriction, you may remove that term.  If a license document contains
475
a further restriction but permits relicensing or conveying under this
476
License, you may add to a covered work material governed by the terms
477
of that license document, provided that the further restriction does
478
not survive such relicensing or conveying.
479

                            
                        
480
  If you add terms to a covered work in accord with this section, you
481
must place, in the relevant source files, a statement of the
482
additional terms that apply to those files, or a notice indicating
483
where to find the applicable terms.
484

                            
                        
485
  Additional terms, permissive or non-permissive, may be stated in the
486
form of a separately written license, or stated as exceptions;
487
the above requirements apply either way.
488

                            
                        
489
  8. Termination.
490

                            
                        
491
  You may not propagate or modify a covered work except as expressly
492
provided under this License.  Any attempt otherwise to propagate or
493
modify it is void, and will automatically terminate your rights under
494
this License (including any patent licenses granted under the third
495
paragraph of section 11).
496

                            
                        
497
  However, if you cease all violation of this License, then your
498
license from a particular copyright holder is reinstated (a)
499
provisionally, unless and until the copyright holder explicitly and
500
finally terminates your license, and (b) permanently, if the copyright
501
holder fails to notify you of the violation by some reasonable means
502
prior to 60 days after the cessation.
503

                            
                        
504
  Moreover, your license from a particular copyright holder is
505
reinstated permanently if the copyright holder notifies you of the
506
violation by some reasonable means, this is the first time you have
507
received notice of violation of this License (for any work) from that
508
copyright holder, and you cure the violation prior to 30 days after
509
your receipt of the notice.
510

                            
                        
511
  Termination of your rights under this section does not terminate the
512
licenses of parties who have received copies or rights from you under
513
this License.  If your rights have been terminated and not permanently
514
reinstated, you do not qualify to receive new licenses for the same
515
material under section 10.
516

                            
                        
517
  9. Acceptance Not Required for Having Copies.
518

                            
                        
519
  You are not required to accept this License in order to receive or
520
run a copy of the Program.  Ancillary propagation of a covered work
521
occurring solely as a consequence of using peer-to-peer transmission
522
to receive a copy likewise does not require acceptance.  However,
523
nothing other than this License grants you permission to propagate or
524
modify any covered work.  These actions infringe copyright if you do
525
not accept this License.  Therefore, by modifying or propagating a
526
covered work, you indicate your acceptance of this License to do so.
527

                            
                        
528
  10. Automatic Licensing of Downstream Recipients.
529

                            
                        
530
  Each time you convey a covered work, the recipient automatically
531
receives a license from the original licensors, to run, modify and
532
propagate that work, subject to this License.  You are not responsible
533
for enforcing compliance by third parties with this License.
534

                            
                        
535
  An "entity transaction" is a transaction transferring control of an
536
organization, or substantially all assets of one, or subdividing an
537
organization, or merging organizations.  If propagation of a covered
538
work results from an entity transaction, each party to that
539
transaction who receives a copy of the work also receives whatever
540
licenses to the work the party's predecessor in interest had or could
541
give under the previous paragraph, plus a right to possession of the
542
Corresponding Source of the work from the predecessor in interest, if
543
the predecessor has it or can get it with reasonable efforts.
544

                            
                        
545
  You may not impose any further restrictions on the exercise of the
546
rights granted or affirmed under this License.  For example, you may
547
not impose a license fee, royalty, or other charge for exercise of
548
rights granted under this License, and you may not initiate litigation
549
(including a cross-claim or counterclaim in a lawsuit) alleging that
550
any patent claim is infringed by making, using, selling, offering for
551
sale, or importing the Program or any portion of it.
552

                            
                        
553
  11. Patents.
554

                            
                        
555
  A "contributor" is a copyright holder who authorizes use under this
556
License of the Program or a work on which the Program is based.  The
557
work thus licensed is called the contributor's "contributor version".
558

                            
                        
559
  A contributor's "essential patent claims" are all patent claims
560
owned or controlled by the contributor, whether already acquired or
561
hereafter acquired, that would be infringed by some manner, permitted
562
by this License, of making, using, or selling its contributor version,
563
but do not include claims that would be infringed only as a
564
consequence of further modification of the contributor version.  For
565
purposes of this definition, "control" includes the right to grant
566
patent sublicenses in a manner consistent with the requirements of
567
this License.
568

                            
                        
569
  Each contributor grants you a non-exclusive, worldwide, royalty-free
570
patent license under the contributor's essential patent claims, to
571
make, use, sell, offer for sale, import and otherwise run, modify and
572
propagate the contents of its contributor version.
573

                            
                        
574
  In the following three paragraphs, a "patent license" is any express
575
agreement or commitment, however denominated, not to enforce a patent
576
(such as an express permission to practice a patent or covenant not to
577
sue for patent infringement).  To "grant" such a patent license to a
578
party means to make such an agreement or commitment not to enforce a
579
patent against the party.
580

                            
                        
581
  If you convey a covered work, knowingly relying on a patent license,
582
and the Corresponding Source of the work is not available for anyone
583
to copy, free of charge and under the terms of this License, through a
584
publicly available network server or other readily accessible means,
585
then you must either (1) cause the Corresponding Source to be so
586
available, or (2) arrange to deprive yourself of the benefit of the
587
patent license for this particular work, or (3) arrange, in a manner
588
consistent with the requirements of this License, to extend the patent
589
license to downstream recipients.  "Knowingly relying" means you have
590
actual knowledge that, but for the patent license, your conveying the
591
covered work in a country, or your recipient's use of the covered work
592
in a country, would infringe one or more identifiable patents in that
593
country that you have reason to believe are valid.
594

                            
                        
595
  If, pursuant to or in connection with a single transaction or
596
arrangement, you convey, or propagate by procuring conveyance of, a
597
covered work, and grant a patent license to some of the parties
598
receiving the covered work authorizing them to use, propagate, modify
599
or convey a specific copy of the covered work, then the patent license
600
you grant is automatically extended to all recipients of the covered
601
work and works based on it.
602

                            
                        
603
  A patent license is "discriminatory" if it does not include within
604
the scope of its coverage, prohibits the exercise of, or is
605
conditioned on the non-exercise of one or more of the rights that are
606
specifically granted under this License.  You may not convey a covered
607
work if you are a party to an arrangement with a third party that is
608
in the business of distributing software, under which you make payment
609
to the third party based on the extent of your activity of conveying
610
the work, and under which the third party grants, to any of the
611
parties who would receive the covered work from you, a discriminatory
612
patent license (a) in connection with copies of the covered work
613
conveyed by you (or copies made from those copies), or (b) primarily
614
for and in connection with specific products or compilations that
615
contain the covered work, unless you entered into that arrangement,
616
or that patent license was granted, prior to 28 March 2007.
617

                            
                        
618
  Nothing in this License shall be construed as excluding or limiting
619
any implied license or other defenses to infringement that may
620
otherwise be available to you under applicable patent law.
621

                            
                        
622
  12. No Surrender of Others' Freedom.
623

                            
                        
624
  If conditions are imposed on you (whether by court order, agreement or
625
otherwise) that contradict the conditions of this License, they do not
626
excuse you from the conditions of this License.  If you cannot convey a
627
covered work so as to satisfy simultaneously your obligations under this
628
License and any other pertinent obligations, then as a consequence you may
629
not convey it at all.  For example, if you agree to terms that obligate you
630
to collect a royalty for further conveying from those to whom you convey
631
the Program, the only way you could satisfy both those terms and this
632
License would be to refrain entirely from conveying the Program.
633

                            
                        
634
  13. Use with the GNU Affero General Public License.
635

                            
                        
636
  Notwithstanding any other provision of this License, you have
637
permission to link or combine any covered work with a work licensed
638
under version 3 of the GNU Affero General Public License into a single
639
combined work, and to convey the resulting work.  The terms of this
640
License will continue to apply to the part which is the covered work,
641
but the special requirements of the GNU Affero General Public License,
642
section 13, concerning interaction through a network will apply to the
643
combination as such.
644

                            
                        
645
  14. Revised Versions of this License.
646

                            
                        
647
  The Free Software Foundation may publish revised and/or new versions of
648
the GNU General Public License from time to time.  Such new versions will
649
be similar in spirit to the present version, but may differ in detail to
650
address new problems or concerns.
651

                            
                        
652
  Each version is given a distinguishing version number.  If the
653
Program specifies that a certain numbered version of the GNU General
654
Public License "or any later version" applies to it, you have the
655
option of following the terms and conditions either of that numbered
656
version or of any later version published by the Free Software
657
Foundation.  If the Program does not specify a version number of the
658
GNU General Public License, you may choose any version ever published
659
by the Free Software Foundation.
660

                            
                        
661
  If the Program specifies that a proxy can decide which future
662
versions of the GNU General Public License can be used, that proxy's
663
public statement of acceptance of a version permanently authorizes you
664
to choose that version for the Program.
665

                            
                        
666
  Later license versions may give you additional or different
667
permissions.  However, no additional obligations are imposed on any
668
author or copyright holder as a result of your choosing to follow a
669
later version.
670

                            
                        
671
  15. Disclaimer of Warranty.
672

                            
                        
673
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
674
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
675
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
676
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
677
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
678
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
679
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
680
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
681

                            
                        
682
  16. Limitation of Liability.
683

                            
                        
684
  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
685
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
686
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
687
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
688
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
689
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
690
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
691
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
692
SUCH DAMAGES.
693

                            
                        
694
  17. Interpretation of Sections 15 and 16.
695

                            
                        
696
  If the disclaimer of warranty and limitation of liability provided
697
above cannot be given local legal effect according to their terms,
698
reviewing courts shall apply local law that most closely approximates
699
an absolute waiver of all civil liability in connection with the
700
Program, unless a warranty or assumption of liability accompanies a
701
copy of the Program in return for a fee.
702

                            
                        
703
                     END OF TERMS AND CONDITIONS
704

                            
                        
705
            How to Apply These Terms to Your New Programs
706

                            
                        
707
  If you develop a new program, and you want it to be of the greatest
708
possible use to the public, the best way to achieve this is to make it
709
free software which everyone can redistribute and change under these terms.
710

                            
                        
711
  To do so, attach the following notices to the program.  It is safest
712
to attach them to the start of each source file to most effectively
713
state the exclusion of warranty; and each file should have at least
714
the "copyright" line and a pointer to where the full notice is found.
715

                            
                        
716
    <one line to give the program's name and a brief idea of what it does.>
717
    Copyright (C) <year>  <name of author>
718

                            
                        
719
    This program is free software: you can redistribute it and/or modify
720
    it under the terms of the GNU General Public License as published by
721
    the Free Software Foundation, either version 3 of the License, or
722
    (at your option) any later version.
723

                            
                        
724
    This program is distributed in the hope that it will be useful,
725
    but WITHOUT ANY WARRANTY; without even the implied warranty of
726
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
727
    GNU General Public License for more details.
728

                            
                        
729
    You should have received a copy of the GNU General Public License
730
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
731

                            
                        
732
Also add information on how to contact you by electronic and paper mail.
733

                            
                        
734
  If the program does terminal interaction, make it output a short
735
notice like this when it starts in an interactive mode:
736

                            
                        
737
    <program>  Copyright (C) <year>  <name of author>
738
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
739
    This is free software, and you are welcome to redistribute it
740
    under certain conditions; type `show c' for details.
741

                            
                        
742
The hypothetical commands `show w' and `show c' should show the appropriate
743
parts of the General Public License.  Of course, your program's commands
744
might be different; for a GUI interface, you would use an "about box".
745

                            
                        
746
  You should also get your employer (if you work as a programmer) or school,
747
if any, to sign a "copyright disclaimer" for the program, if necessary.
748
For more information on this, and how to apply and follow the GNU GPL, see
749
<http://www.gnu.org/licenses/>.
750

                            
                        
751
  The GNU General Public License does not permit incorporating your program
752
into proprietary programs.  If your program is a subroutine library, you
753
may consider it more useful to permit linking proprietary applications with
754
the library.  If this is what you want to do, use the GNU Lesser General
755
Public License instead of this License.  But first, please read
756
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
757

                            
                        
758
*/
759

                            
                        

Lines covered: 8 / 23 (34.8%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import {IERC3156FlashBorrower} from "../../Interfaces/IERC3156FlashBorrower.sol";
4
import {IERC20} from "../../Dependencies/IERC20.sol";
5

                            
                        
6
contract Actor is IERC3156FlashBorrower {
7
    address[] internal tokens;
8
    address[] internal callers;
9

                            
                        
10
    constructor(address[] memory _tokens, address[] memory _callers) payable {
11
        tokens = _tokens;
12
        callers = _callers;
13
        for (uint256 i = 0; i < tokens.length; i++) {
14
            IERC20(tokens[i]).approve(callers[i], type(uint256).max);
15
        }
16
    }
17

                            
                        
18
    function proxy(
19
        address _target,
20
        bytes memory _calldata
21
    ) public returns (bool success, bytes memory returnData) {
22
        (success, returnData) = address(_target).call(_calldata);
23
    }
24

                            
                        
25
    function proxy(
26
        address _target,
27
        bytes memory _calldata,
28
        uint256 value
29
    ) public returns (bool success, bytes memory returnData) {
30
        (success, returnData) = address(_target).call{value: value}(_calldata);
31
    }
32

                            
                        
33
    receive() external payable {}
34

                            
                        
35
    // callback for flashloan
36
    function onFlashLoan(
37
        address,
38
        address token,
39
        uint256 amount,
40
        uint256 fee,
41
        bytes calldata data
42
    ) external override returns (bytes32) {
43
        bool isValidCaller = false;
44
        for (uint256 i = 0; i < tokens.length; i++) {
45
            if (token == tokens[i]) {
46
                isValidCaller = msg.sender == callers[i];
47
                break;
48
            }
49
        }
50
        require(isValidCaller, "Invalid caller");
51

                            
                        
52
        if (data.length != 0) {
53
            (address[] memory _targets, bytes[] memory _calldatas) = abi.decode(
54
                data,
55
                (address[], bytes[])
56
            );
57
            for (uint256 i = 0; i < _targets.length; ++i) {
58
                (bool success, ) = address(_targets[i]).call(_calldatas[i]);
59
                require(success);
60
            }
61
        }
62

                            
                        
63
        IERC20(token).approve(msg.sender, amount + fee);
64

                            
                        
65
        return keccak256("ERC3156FlashBorrower.onFlashLoan");
66
    }
67
}
68

                            
                        

Lines covered: 21 / 31 (67.7%)

1
pragma solidity 0.8.17;
2

                            
                        
3
abstract contract Asserts {
4
    event L1(uint256);
5
    event L2(uint256, uint256);
6
    event L3(uint256, uint256, uint256);
7
    event L4(uint256, uint256, uint256, uint256);
8

                            
                        
9
    function gt(uint256 a, uint256 b, string memory reason) internal virtual;
10

                            
                        
11
    function gte(uint256 a, uint256 b, string memory reason) internal virtual;
12

                            
                        
13
    function lt(uint256 a, uint256 b, string memory reason) internal virtual;
14

                            
                        
15
    function lte(uint256 a, uint256 b, string memory reason) internal virtual;
16

                            
                        
17
    function eq(uint256 a, uint256 b, string memory reason) internal virtual;
18

                            
                        
19
    function t(bool b, string memory reason) internal virtual;
20

                            
                        
21
    function between(uint256 value, uint256 low, uint256 high) internal virtual returns (uint256);
22

                            
                        
23
    function isApproximateEq(
24
        uint256 _num1,
25
        uint256 _num2,
26
        uint256 _tolerance
27
    ) internal pure returns (bool) {
28
        return diffPercent(_num1, _num2) <= _tolerance;
29
    }
30

                            
                        
31
    function diffPercent(uint256 _num1, uint256 _num2) internal pure returns (uint256) {
32
        if (_num1 == _num2) return 0;
33
        else if (_num1 > _num2) {
34
            return ((_num1 - _num2) * 1e18) / ((_num1 + _num2) / 2);
35
        } else {
36
            return ((_num2 - _num1) * 1e18) / ((_num1 + _num2) / 2);
37
        }
38
    }
39

                            
                        
40
    // https://ethereum.stackexchange.com/a/83577
41
    function _getRevertMsg(bytes memory returnData) internal pure returns (string memory) {
42
        // Check that the data has the right size: 4 bytes for signature + 32 bytes for panic code
43
        if (returnData.length == 4 + 32) {
44
            // Check that the data starts with the Panic signature
45
            bytes4 panicSignature = bytes4(keccak256(bytes("Panic(uint256)")));
46
            for (uint i = 0; i < 4; i++) {
47
                if (returnData[i] != panicSignature[i]) return "Undefined signature";
48
            }
49

                            
                        
50
            uint256 panicCode;
51
            for (uint i = 4; i < 36; i++) {
52
                panicCode = panicCode << 8;
53
                panicCode |= uint8(returnData[i]);
54
            }
55

                            
                        
56
            // Now convert the panic code into its string representation
57
            if (panicCode == 17) {
58
                return "Panic(17)";
59
            }
60

                            
                        
61
            // Add other panic codes as needed or return a generic "Unknown panic"
62
            return "Undefined panic code";
63
        }
64

                            
                        
65
        // If the returnData length is less than 68, then the transaction failed silently (without a revert message)
66
        if (returnData.length < 68) return "Transaction reverted silently";
67

                            
                        
68
        assembly {
69
            // Slice the sighash.
70
            returnData := add(returnData, 0x04)
71
        }
72
        return abi.decode(returnData, (string)); // All that remains is the revert string
73
    }
74

                            
                        
75
    function _isRevertReasonEqual(
76
        bytes memory returnData,
77
        string memory reason
78
    ) internal pure returns (bool) {
79
        return (keccak256(abi.encodePacked(_getRevertMsg(returnData))) ==
80
            keccak256(abi.encodePacked(reason)));
81
    }
82

                            
                        
83
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
84
        return a >= b ? a : b;
85
    }
86

                            
                        
87
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
88
        return a < b ? a : b;
89
    }
90

                            
                        
91
    function assertRevertReasonNotEqual(bytes memory returnData, string memory reason) internal {
92
        bool isEqual = _isRevertReasonEqual(returnData, reason);
93
        t(!isEqual, reason);
94
    }
95

                            
                        
96
    function assertRevertReasonEqual(bytes memory returnData, string memory reason) internal {
97
        bool isEqual = _isRevertReasonEqual(returnData, reason);
98
        t(isEqual, reason);
99
    }
100

                            
                        
101
    function assertRevertReasonEqual(
102
        bytes memory returnData,
103
        string memory reason1,
104
        string memory reason2
105
    ) internal {
106
        bool isEqual = _isRevertReasonEqual(returnData, reason1) ||
107
            _isRevertReasonEqual(returnData, reason2);
108
        t(isEqual, string.concat(reason1, " OR ", reason2));
109
    }
110

                            
                        
111
    function assertRevertReasonEqual(
112
        bytes memory returnData,
113
        string memory reason1,
114
        string memory reason2,
115
        string memory reason3
116
    ) internal {
117
        bool isEqual = _isRevertReasonEqual(returnData, reason1) ||
118
            _isRevertReasonEqual(returnData, reason2) ||
119
            _isRevertReasonEqual(returnData, reason3);
120
        t(isEqual, string.concat(reason1, " OR ", reason2, " OR ", reason3));
121
    }
122

                            
                        
123
    function assertRevertReasonEqual(
124
        bytes memory returnData,
125
        string memory reason1,
126
        string memory reason2,
127
        string memory reason3,
128
        string memory reason4
129
    ) internal {
130
        bool isEqual = _isRevertReasonEqual(returnData, reason1) ||
131
            _isRevertReasonEqual(returnData, reason2) ||
132
            _isRevertReasonEqual(returnData, reason3) ||
133
            _isRevertReasonEqual(returnData, reason4);
134
        t(isEqual, string.concat(reason1, " OR ", reason2, " OR ", reason3, " OR ", reason4));
135
    }
136
}
137

                            
                        

Lines covered: 81 / 81 (100.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import {Pretty, Strings} from "../Pretty.sol";
4
import {BaseStorageVariables} from "../BaseStorageVariables.sol";
5

                            
                        
6
abstract contract BeforeAfter is BaseStorageVariables {
7
    using Strings for string;
8
    using Pretty for uint256;
9
    using Pretty for int256;
10
    using Pretty for bool;
11

                            
                        
12
    struct Vars {
13
        uint256 nicrBefore;
14
        uint256 nicrAfter;
15
        uint256 icrBefore;
16
        uint256 icrAfter;
17
        uint256 newIcrBefore;
18
        uint256 newIcrAfter;
19
        uint256 feeSplitBefore;
20
        uint256 feeSplitAfter;
21
        uint256 feeRecipientTotalCollBefore;
22
        uint256 feeRecipientTotalCollAfter;
23
        uint256 feeRecipientCollSharesBefore;
24
        uint256 feeRecipientCollSharesAfter;
25
        uint256 actorCollBefore;
26
        uint256 actorCollAfter;
27
        uint256 actorEbtcBefore;
28
        uint256 actorEbtcAfter;
29
        uint256 actorCdpCountBefore;
30
        uint256 actorCdpCountAfter;
31
        uint256 cdpCollBefore;
32
        uint256 cdpCollAfter;
33
        uint256 cdpDebtBefore;
34
        uint256 cdpDebtAfter;
35
        uint256 liquidatorRewardSharesBefore;
36
        uint256 liquidatorRewardSharesAfter;
37
        uint256 sortedCdpsSizeBefore;
38
        uint256 sortedCdpsSizeAfter;
39
        uint256 cdpStatusBefore;
40
        uint256 cdpStatusAfter;
41
        uint256 tcrBefore;
42
        uint256 tcrAfter;
43
        uint256 newTcrBefore;
44
        uint256 newTcrAfter;
45
        uint256 ebtcTotalSupplyBefore;
46
        uint256 ebtcTotalSupplyAfter;
47
        uint256 ethPerShareBefore;
48
        uint256 ethPerShareAfter;
49
        uint256 activePoolCollBefore;
50
        uint256 activePoolCollAfter;
51
        uint256 activePoolDebtBefore;
52
        uint256 activePoolDebtAfter;
53
        uint256 collSurplusPoolBefore;
54
        uint256 collSurplusPoolAfter;
55
        uint256 priceBefore;
56
        uint256 priceAfter;
57
        bool isRecoveryModeBefore;
58
        bool isRecoveryModeAfter;
59
        uint256 lastGracePeriodStartTimestampBefore;
60
        uint256 lastGracePeriodStartTimestampAfter;
61
        bool lastGracePeriodStartTimestampIsSetBefore;
62
        bool lastGracePeriodStartTimestampIsSetAfter;
63
        bool hasGracePeriodPassedBefore;
64
        bool hasGracePeriodPassedAfter;
65
        uint256 systemDebtRedistributionIndexBefore;
66
        uint256 systemDebtRedistributionIndexAfter;
67
    }
68

                            
                        
69
    Vars vars;
70
    struct Cdp {
71
        bytes32 id;
72
        uint256 icr;
73
    }
74

                            
                        
75
    function _before(bytes32 _cdpId) internal {
76
        vars.priceBefore = priceFeedMock.fetchPrice();
77

                            
                        
78
        (uint256 debtBefore, , ) = cdpManager.getDebtAndCollShares(_cdpId);
79

                            
                        
80
        vars.nicrBefore = _cdpId != bytes32(0) ? crLens.quoteRealNICR(_cdpId) : 0;
81
        vars.icrBefore = _cdpId != bytes32(0) ? cdpManager.getICR(_cdpId, vars.priceBefore) : 0;
82
        vars.cdpCollBefore = _cdpId != bytes32(0) ? cdpManager.getCdpCollShares(_cdpId) : 0;
83
        vars.cdpDebtBefore = _cdpId != bytes32(0) ? debtBefore : 0;
84
        vars.liquidatorRewardSharesBefore = _cdpId != bytes32(0)
85
            ? cdpManager.getCdpLiquidatorRewardShares(_cdpId)
86
            : 0;
87
        vars.cdpStatusBefore = _cdpId != bytes32(0) ? cdpManager.getCdpStatus(_cdpId) : 0;
88

                            
                        
89
        vars.isRecoveryModeBefore = crLens.quoteCheckRecoveryMode() == 1; /// @audit crLens
90
        (vars.feeSplitBefore, , ) = collateral.getPooledEthByShares(cdpManager.DECIMAL_PRECISION()) >
91
            cdpManager.stEthIndex()
92
            ? cdpManager.calcFeeUponStakingReward(
93
                collateral.getPooledEthByShares(cdpManager.DECIMAL_PRECISION()),
94
                cdpManager.stEthIndex()
95
            )
96
            : (0, 0, 0);
97
        vars.feeRecipientTotalCollBefore =
98
            activePool.getFeeRecipientClaimableCollShares() +
99
            collateral.balanceOf(activePool.feeRecipientAddress());
100
        vars.feeRecipientCollSharesBefore = activePool.getFeeRecipientClaimableCollShares();
101
        vars.actorCollBefore = collateral.balanceOf(address(actor));
102
        vars.actorEbtcBefore = eBTCToken.balanceOf(address(actor));
103
        vars.actorCdpCountBefore = sortedCdps.cdpCountOf(address(actor));
104
        vars.sortedCdpsSizeBefore = sortedCdps.getSize();
105
        vars.tcrBefore = cdpManager.getTCR(vars.priceBefore);
106
        vars.ebtcTotalSupplyBefore = eBTCToken.totalSupply();
107
        vars.ethPerShareBefore = collateral.getEthPerShare();
108
        vars.activePoolDebtBefore = activePool.getSystemDebt();
109
        vars.activePoolCollBefore = activePool.getSystemCollShares();
110
        vars.collSurplusPoolBefore = collSurplusPool.getTotalSurplusCollShares();
111
        vars.lastGracePeriodStartTimestampBefore = cdpManager.lastGracePeriodStartTimestamp();
112
        vars.lastGracePeriodStartTimestampIsSetBefore =
113
            cdpManager.lastGracePeriodStartTimestamp() != cdpManager.UNSET_TIMESTAMP();
114
        vars.hasGracePeriodPassedBefore =
115
            cdpManager.lastGracePeriodStartTimestamp() != cdpManager.UNSET_TIMESTAMP() &&
116
            block.timestamp >
117
            cdpManager.lastGracePeriodStartTimestamp() + cdpManager.recoveryModeGracePeriod();
118
        vars.systemDebtRedistributionIndexBefore = cdpManager.systemDebtRedistributionIndex();
119

                            
                        
120
        vars.newTcrBefore = crLens.quoteRealTCR();
121
        vars.newIcrBefore = crLens.quoteRealICR(_cdpId);
122
    }
123

                            
                        
124
    function _after(bytes32 _cdpId) internal {
125
        vars.priceAfter = priceFeedMock.fetchPrice();
126

                            
                        
127
        vars.nicrAfter = _cdpId != bytes32(0) ? crLens.quoteRealNICR(_cdpId) : 0;
128
        vars.icrAfter = _cdpId != bytes32(0) ? cdpManager.getICR(_cdpId, vars.priceAfter) : 0;
129
        vars.cdpCollAfter = _cdpId != bytes32(0) ? cdpManager.getCdpCollShares(_cdpId) : 0;
130
        vars.cdpDebtAfter = _cdpId != bytes32(0) ? cdpManager.getCdpDebt(_cdpId) : 0;
131
        vars.liquidatorRewardSharesAfter = _cdpId != bytes32(0)
132
            ? cdpManager.getCdpLiquidatorRewardShares(_cdpId)
133
            : 0;
134
        vars.cdpStatusAfter = _cdpId != bytes32(0) ? cdpManager.getCdpStatus(_cdpId) : 0;
135

                            
                        
136
        vars.isRecoveryModeAfter = cdpManager.checkRecoveryMode(vars.priceAfter); /// @audit This is fine as is because after the system is synched
137
        (vars.feeSplitAfter, , ) = collateral.getPooledEthByShares(cdpManager.DECIMAL_PRECISION()) >
138
            cdpManager.stEthIndex()
139
            ? cdpManager.calcFeeUponStakingReward(
140
                collateral.getPooledEthByShares(cdpManager.DECIMAL_PRECISION()),
141
                cdpManager.stEthIndex()
142
            )
143
            : (0, 0, 0);
144
        vars.feeRecipientTotalCollAfter =
145
            activePool.getFeeRecipientClaimableCollShares() +
146
            collateral.balanceOf(activePool.feeRecipientAddress());
147
        vars.feeRecipientCollSharesAfter = activePool.getFeeRecipientClaimableCollShares();
148
        vars.actorCollAfter = collateral.balanceOf(address(actor));
149
        vars.actorEbtcAfter = eBTCToken.balanceOf(address(actor));
150
        vars.actorCdpCountAfter = sortedCdps.cdpCountOf(address(actor));
151
        vars.sortedCdpsSizeAfter = sortedCdps.getSize();
152
        vars.tcrAfter = cdpManager.getTCR(vars.priceAfter);
153
        vars.ebtcTotalSupplyAfter = eBTCToken.totalSupply();
154
        vars.ethPerShareAfter = collateral.getEthPerShare();
155
        vars.activePoolDebtAfter = activePool.getSystemDebt();
156
        vars.activePoolCollAfter = activePool.getSystemCollShares();
157
        vars.collSurplusPoolAfter = collSurplusPool.getTotalSurplusCollShares();
158
        vars.lastGracePeriodStartTimestampAfter = cdpManager.lastGracePeriodStartTimestamp();
159
        vars.lastGracePeriodStartTimestampIsSetAfter =
160
            cdpManager.lastGracePeriodStartTimestamp() != cdpManager.UNSET_TIMESTAMP();
161
        vars.hasGracePeriodPassedAfter =
162
            cdpManager.lastGracePeriodStartTimestamp() != cdpManager.UNSET_TIMESTAMP() &&
163
            block.timestamp >
164
            cdpManager.lastGracePeriodStartTimestamp() + cdpManager.recoveryModeGracePeriod();
165
        vars.systemDebtRedistributionIndexAfter = cdpManager.systemDebtRedistributionIndex();
166

                            
                        
167
        vars.newTcrAfter = crLens.quoteRealTCR();
168
        vars.newIcrAfter = crLens.quoteRealICR(_cdpId);
169
    }
170

                            
                        
171
    function _diff() internal view returns (string memory log) {
172
        log = string("\n\t\t\t\tBefore\t\t\tAfter\n");
173
        if (vars.activePoolCollBefore != vars.activePoolCollAfter) {
174
            log = log
175
                .concat("activePoolColl\t\t\t")
176
                .concat(vars.activePoolCollBefore.pretty())
177
                .concat("\t")
178
                .concat(vars.activePoolCollAfter.pretty())
179
                .concat("\n");
180
        }
181
        if (vars.collSurplusPoolBefore != vars.collSurplusPoolAfter) {
182
            log = log
183
                .concat("collSurplusPool\t\t\t")
184
                .concat(vars.collSurplusPoolBefore.pretty())
185
                .concat("\t")
186
                .concat(vars.collSurplusPoolAfter.pretty())
187
                .concat("\n");
188
        }
189
        if (vars.nicrBefore != vars.nicrAfter) {
190
            log = log
191
                .concat("nicr\t\t\t\t")
192
                .concat(vars.nicrBefore.pretty())
193
                .concat("\t")
194
                .concat(vars.nicrAfter.pretty())
195
                .concat("\n");
196
        }
197
        if (vars.icrBefore != vars.icrAfter) {
198
            log = log
199
                .concat("icr\t\t\t\t")
200
                .concat(vars.icrBefore.pretty())
201
                .concat("\t")
202
                .concat(vars.icrAfter.pretty())
203
                .concat("\n");
204
        }
205
        if (vars.newIcrBefore != vars.newIcrAfter) {
206
            log = log
207
                .concat("newIcr\t\t\t\t")
208
                .concat(vars.newIcrBefore.pretty())
209
                .concat("\t")
210
                .concat(vars.newIcrAfter.pretty())
211
                .concat("\n");
212
        }
213
        if (vars.feeSplitBefore != vars.feeSplitAfter) {
214
            log = log
215
                .concat("feeSplit\t\t\t\t")
216
                .concat(vars.feeSplitBefore.pretty())
217
                .concat("\t")
218
                .concat(vars.feeSplitAfter.pretty())
219
                .concat("\n");
220
        }
221
        if (vars.feeRecipientTotalCollBefore != vars.feeRecipientTotalCollAfter) {
222
            log = log
223
                .concat("feeRecipientTotalColl\t")
224
                .concat(vars.feeRecipientTotalCollBefore.pretty())
225
                .concat("\t")
226
                .concat(vars.feeRecipientTotalCollAfter.pretty())
227
                .concat("\n");
228
        }
229
        if (vars.actorCollBefore != vars.actorCollAfter) {
230
            log = log
231
                .concat("actorColl\t\t\t\t")
232
                .concat(vars.actorCollBefore.pretty())
233
                .concat("\t")
234
                .concat(vars.actorCollAfter.pretty())
235
                .concat("\n");
236
        }
237
        if (vars.actorEbtcBefore != vars.actorEbtcAfter) {
238
            log = log
239
                .concat("actorEbtc\t\t\t\t")
240
                .concat(vars.actorEbtcBefore.pretty())
241
                .concat("\t")
242
                .concat(vars.actorEbtcAfter.pretty())
243
                .concat("\n");
244
        }
245
        if (vars.actorCdpCountBefore != vars.actorCdpCountAfter) {
246
            log = log
247
                .concat("actorCdpCount\t\t\t")
248
                .concat(vars.actorCdpCountBefore.pretty())
249
                .concat("\t")
250
                .concat(vars.actorCdpCountAfter.pretty())
251
                .concat("\n");
252
        }
253
        if (vars.cdpCollBefore != vars.cdpCollAfter) {
254
            log = log
255
                .concat("cdpColl\t\t\t\t")
256
                .concat(vars.cdpCollBefore.pretty())
257
                .concat("\t")
258
                .concat(vars.cdpCollAfter.pretty())
259
                .concat("\n");
260
        }
261
        if (vars.cdpDebtBefore != vars.cdpDebtAfter) {
262
            log = log
263
                .concat("cdpDebt\t\t\t\t")
264
                .concat(vars.cdpDebtBefore.pretty())
265
                .concat("\t")
266
                .concat(vars.cdpDebtAfter.pretty())
267
                .concat("\n");
268
        }
269
        if (vars.liquidatorRewardSharesBefore != vars.liquidatorRewardSharesAfter) {
270
            log = log
271
                .concat("liquidatorRewardShares\t\t")
272
                .concat(vars.liquidatorRewardSharesBefore.pretty())
273
                .concat("\t")
274
                .concat(vars.liquidatorRewardSharesAfter.pretty())
275
                .concat("\n");
276
        }
277
        if (vars.sortedCdpsSizeBefore != vars.sortedCdpsSizeAfter) {
278
            log = log
279
                .concat("sortedCdpsSize\t\t\t")
280
                .concat(vars.sortedCdpsSizeBefore.pretty(0))
281
                .concat("\t\t\t")
282
                .concat(vars.sortedCdpsSizeAfter.pretty(0))
283
                .concat("\n");
284
        }
285
        if (vars.cdpStatusBefore != vars.cdpStatusAfter) {
286
            log = log
287
                .concat("cdpStatus\t\t\t")
288
                .concat(vars.cdpStatusBefore.pretty(0))
289
                .concat("\t\t\t")
290
                .concat(vars.cdpStatusAfter.pretty(0))
291
                .concat("\n");
292
        }
293
        if (vars.tcrBefore != vars.tcrAfter) {
294
            log = log
295
                .concat("tcr\t\t\t\t")
296
                .concat(vars.tcrBefore.pretty())
297
                .concat("\t")
298
                .concat(vars.tcrAfter.pretty())
299
                .concat("\n");
300
        }
301
        if (vars.newTcrBefore != vars.newTcrAfter) {
302
            log = log
303
                .concat("newTcr\t\t\t\t")
304
                .concat(vars.newTcrBefore.pretty())
305
                .concat("\t")
306
                .concat(vars.newTcrAfter.pretty())
307
                .concat("\n");
308
        }
309
        if (vars.ebtcTotalSupplyBefore != vars.ebtcTotalSupplyAfter) {
310
            log = log
311
                .concat("ebtcTotalSupply\t\t\t")
312
                .concat(vars.ebtcTotalSupplyBefore.pretty())
313
                .concat("\t")
314
                .concat(vars.ebtcTotalSupplyAfter.pretty())
315
                .concat("\n");
316
        }
317
        if (vars.ethPerShareBefore != vars.ethPerShareAfter) {
318
            log = log
319
                .concat("ethPerShare\t\t\t")
320
                .concat(vars.ethPerShareBefore.pretty())
321
                .concat("\t")
322
                .concat(vars.ethPerShareAfter.pretty())
323
                .concat("\n");
324
        }
325
        if (vars.isRecoveryModeBefore != vars.isRecoveryModeAfter) {
326
            log = log
327
                .concat("isRecoveryMode\t\t\t")
328
                .concat(vars.isRecoveryModeBefore.pretty())
329
                .concat("\t")
330
                .concat(vars.isRecoveryModeAfter.pretty())
331
                .concat("\n");
332
        }
333
        if (vars.lastGracePeriodStartTimestampBefore != vars.lastGracePeriodStartTimestampAfter) {
334
            log = log
335
                .concat("lastGracePeriodStartTimestamp\t")
336
                .concat(vars.lastGracePeriodStartTimestampBefore.pretty())
337
                .concat("\t")
338
                .concat(vars.lastGracePeriodStartTimestampAfter.pretty())
339
                .concat("\n");
340
        }
341
        if (
342
            vars.lastGracePeriodStartTimestampIsSetBefore !=
343
            vars.lastGracePeriodStartTimestampIsSetAfter
344
        ) {
345
            log = log
346
                .concat("lastGracePeriodStartTimestampIsSet\t")
347
                .concat(vars.lastGracePeriodStartTimestampIsSetBefore.pretty())
348
                .concat("\t")
349
                .concat(vars.lastGracePeriodStartTimestampIsSetAfter.pretty())
350
                .concat("\n");
351
        }
352
        if (vars.hasGracePeriodPassedBefore != vars.hasGracePeriodPassedAfter) {
353
            log = log
354
                .concat("hasGracePeriodPassed\t\t")
355
                .concat(vars.hasGracePeriodPassedBefore.pretty())
356
                .concat("\t\t\t")
357
                .concat(vars.hasGracePeriodPassedAfter.pretty())
358
                .concat("\n");
359
        }
360
        if (vars.systemDebtRedistributionIndexBefore != vars.systemDebtRedistributionIndexAfter) {
361
            log = log
362
                .concat("systemDebtRedistributionIndex\t\t")
363
                .concat(vars.systemDebtRedistributionIndexBefore.pretty())
364
                .concat("\t")
365
                .concat(vars.systemDebtRedistributionIndexAfter.pretty())
366
                .concat("\n");
367
        }
368
    }
369
}
370

                            
                        

Lines covered: 189 / 200 (94.5%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import "@crytic/properties/contracts/util/PropertiesConstants.sol";
4

                            
                        
5
import {ICollateralToken} from "../../Dependencies/ICollateralToken.sol";
6
import {LiquityMath} from "../../Dependencies/LiquityMath.sol";
7
import {ActivePool} from "../../ActivePool.sol";
8
import {EBTCToken} from "../../EBTCToken.sol";
9
import {BorrowerOperations} from "../../BorrowerOperations.sol";
10
import {CdpManager} from "../../CdpManager.sol";
11
import {SortedCdps} from "../../SortedCdps.sol";
12
import {Asserts} from "./Asserts.sol";
13
import {CollSurplusPool} from "../../CollSurplusPool.sol";
14
import {PriceFeedTestnet} from "../testnet/PriceFeedTestnet.sol";
15
import {ICdpManagerData} from "../../Interfaces/ICdpManagerData.sol";
16
import {BeforeAfter} from "./BeforeAfter.sol";
17
import {PropertiesDescriptions} from "./PropertiesDescriptions.sol";
18
import {CRLens} from "../../CRLens.sol";
19
import {LiquidationSequencer} from "../../LiquidationSequencer.sol";
20
import {SyncedLiquidationSequencer} from "../../SyncedLiquidationSequencer.sol";
21

                            
                        
22
abstract contract Properties is BeforeAfter, PropertiesDescriptions, Asserts, PropertiesConstants {
23
    function invariant_AP_01(
24
        ICollateralToken collateral,
25
        ActivePool activePool
26
    ) internal view returns (bool) {
27
        return (collateral.sharesOf(address(activePool)) >= activePool.getSystemCollShares());
28
    }
29

                            
                        
30
    function invariant_AP_02(
31
        CdpManager cdpManager,
32
        ActivePool activePool
33
    ) internal view returns (bool) {
34
        return cdpManager.getActiveCdpsCount() > 0 ? activePool.getSystemCollShares() > 0 : true;
35
    }
36

                            
                        
37
    function invariant_AP_03(
38
        EBTCToken eBTCToken,
39
        ActivePool activePool
40
    ) internal view returns (bool) {
41
        return (eBTCToken.totalSupply() == activePool.getSystemDebt());
42
    }
43

                            
                        
44
    function invariant_AP_04(
45
        CdpManager cdpManager,
46
        ActivePool activePool,
47
        uint256 diff_tolerance
48
    ) internal view returns (bool) {
49
        uint256 _cdpCount = cdpManager.getActiveCdpsCount();
50
        uint256 _sum;
51
        for (uint256 i = 0; i < _cdpCount; ++i) {
52
            (, uint256 _coll, ) = cdpManager.getDebtAndCollShares(cdpManager.CdpIds(i));
53
            _sum += _coll;
54
        }
55
        uint256 _activeColl = activePool.getSystemCollShares();
56
        uint256 _diff = _sum > _activeColl ? (_sum - _activeColl) : (_activeColl - _sum);
57
        return (_diff * 1e18 <= diff_tolerance * _activeColl);
58
    }
59

                            
                        
60
    function invariant_AP_05(
61
        CdpManager cdpManager,
62
        uint256 diff_tolerance
63
    ) internal view returns (bool) {
64
        uint256 _cdpCount = cdpManager.getActiveCdpsCount();
65
        uint256 _sum;
66
        for (uint256 i = 0; i < _cdpCount; ++i) {
67
            (uint256 _debt, , ) = cdpManager.getDebtAndCollShares(cdpManager.CdpIds(i));
68
            _sum += _debt;
69
        }
70

                            
                        
71
        bool oldCheck = isApproximateEq(_sum, cdpManager.getSystemDebt(), diff_tolerance);
72
        // New check ensures this is above 1000 wei
73
        bool newCheck = cdpManager.getSystemDebt() - _sum > 1_000;
74
        // @audit We have an instance of getting above 1e18 in rounding error, see `testBrokenInvariantFive`
75
        return oldCheck || !newCheck;
76
    }
77

                            
                        
78
    function invariant_CDPM_01(
79
        CdpManager cdpManager,
80
        SortedCdps sortedCdps
81
    ) internal view returns (bool) {
82
        return (cdpManager.getActiveCdpsCount() == sortedCdps.getSize());
83
    }
84

                            
                        
85
    function invariant_CDPM_02(CdpManager cdpManager) internal view returns (bool) {
86
        uint256 _cdpCount = cdpManager.getActiveCdpsCount();
87
        uint256 _sum;
88
        for (uint256 i = 0; i < _cdpCount; ++i) {
89
            _sum += cdpManager.getCdpStake(cdpManager.CdpIds(i));
90
        }
91
        return (_sum == cdpManager.totalStakes());
92
    }
93

                            
                        
94
    function invariant_CDPM_03(CdpManager cdpManager) internal view returns (bool) {
95
        uint256 _cdpCount = cdpManager.getActiveCdpsCount();
96
        uint256 systemStEthFeePerUnitIndex = cdpManager.systemStEthFeePerUnitIndex();
97
        for (uint256 i = 0; i < _cdpCount; ++i) {
98
            if (systemStEthFeePerUnitIndex < cdpManager.stEthFeePerUnitIndex(cdpManager.CdpIds(i))) {
99
                return false;
100
            }
101
        }
102
        return true;
103
    }
104

                            
                        
105
    /** TODO: See EchidnaToFoundry._getValue */
106
    function invariant_CDPM_04(Vars memory vars) internal view returns (bool) {
107
        uint256 beforeValue = ((vars.activePoolCollBefore +
108
            vars.collSurplusPoolBefore +
109
            vars.feeRecipientTotalCollBefore) * vars.priceBefore) /
110
            1e18 -
111
            vars.activePoolDebtBefore;
112

                            
                        
113
        uint256 afterValue = ((vars.activePoolCollAfter +
114
            vars.collSurplusPoolAfter +
115
            vars.feeRecipientTotalCollAfter) * vars.priceAfter) /
116
            1e18 -
117
            vars.activePoolDebtAfter;
118

                            
                        
119
        return afterValue >= beforeValue || isApproximateEq(afterValue, beforeValue, 0.01e18);
120
    }
121

                            
                        
122
    function invariant_CSP_01(
123
        ICollateralToken collateral,
124
        CollSurplusPool collSurplusPool
125
    ) internal view returns (bool) {
126
        return
127
            collateral.sharesOf(address(collSurplusPool)) >=
128
            collSurplusPool.getTotalSurplusCollShares();
129
    }
130

                            
                        
131
    function invariant_CSP_02(CollSurplusPool collSurplusPool) internal view returns (bool) {
132
        uint256 sum;
133

                            
                        
134
        // NOTE: See PropertiesConstants
135
        // We only have 3 actors so just set these up
136
        sum += collSurplusPool.getSurplusCollShares(address(actors[USER1]));
137
        sum += collSurplusPool.getSurplusCollShares(address(actors[USER2]));
138
        sum += collSurplusPool.getSurplusCollShares(address(actors[USER3]));
139

                            
                        
140
        return sum == collSurplusPool.getTotalSurplusCollShares();
141
    }
142

                            
                        
143
    event L(string, uint);
144

                            
                        
145
    function invariant_SL_01(CdpManager cdpManager, SortedCdps sortedCdps) internal returns (bool) {
146
        bytes32 currentCdp = sortedCdps.getFirst();
147
        bytes32 nextCdp = sortedCdps.getNext(currentCdp);
148

                            
                        
149
        while (currentCdp != bytes32(0) && nextCdp != bytes32(0) && currentCdp != nextCdp) {
150
            // TODO remove tolerance once proper fix has been applied
151
            uint256 nicrNext = cdpManager.getNominalICR(nextCdp);
152
            uint256 nicrCurrent = cdpManager.getNominalICR(currentCdp);
153
            emit L("NICR next", nicrNext);
154
            emit L("NICR curr", nicrCurrent);
155
            if (nicrNext > nicrCurrent && diffPercent(nicrNext, nicrCurrent) > 0.01e18) {
156
                return false;
157
            }
158

                            
                        
159
            currentCdp = nextCdp;
160
            nextCdp = sortedCdps.getNext(currentCdp);
161
        }
162

                            
                        
163
        return true;
164
    }
165

                            
                        
166
    function invariant_SL_02(
167
        CdpManager cdpManager,
168
        SortedCdps sortedCdps,
169
        PriceFeedTestnet priceFeedMock
170
    ) internal view returns (bool) {
171
        bytes32 _first = sortedCdps.getFirst();
172
        uint256 _price = priceFeedMock.getPrice();
173
        uint256 _firstICR = cdpManager.getICR(_first, _price);
174
        uint256 _TCR = cdpManager.getTCR(_price);
175

                            
                        
176
        if (
177
            _first != sortedCdps.dummyId() &&
178
            _firstICR < _TCR &&
179
            diffPercent(_firstICR, _TCR) > 0.01e18
180
        ) {
181
            return false;
182
        }
183
        return true;
184
    }
185

                            
                        
186
    function invariant_SL_03(
187
        CdpManager cdpManager,
188
        PriceFeedTestnet priceFeedMock,
189
        SortedCdps sortedCdps
190
    ) internal view returns (bool) {
191
        bytes32 currentCdp = sortedCdps.getFirst();
192

                            
                        
193
        uint256 _price = priceFeedMock.getPrice();
194
        if (_price == 0) return true;
195

                            
                        
196
        while (currentCdp != bytes32(0)) {
197
            // Status
198
            if (
199
                ICdpManagerData.Status(cdpManager.getCdpStatus(currentCdp)) !=
200
                ICdpManagerData.Status.active
201
            ) {
202
                return false;
203
            }
204

                            
                        
205
            // Stake > 0
206
            if (cdpManager.getCdpStake(currentCdp) == 0) {
207
                return false;
208
            }
209

                            
                        
210
            currentCdp = sortedCdps.getNext(currentCdp);
211
        }
212
        return true;
213
    }
214

                            
                        
215
    uint256 NICR_ERROR_THRESHOLD = 1e8;
216

                            
                        
217
    function invariant_SL_05(CRLens crLens, SortedCdps sortedCdps) internal returns (bool) {
218
        bytes32 currentCdp = sortedCdps.getFirst();
219

                            
                        
220
        uint256 newIcrPrevious = type(uint256).max;
221

                            
                        
222
        while (currentCdp != bytes32(0)) {
223
            uint256 newIcr = crLens.quoteRealICR(currentCdp);
224
            if (newIcr > newIcrPrevious) {
225
                /// @audit Precision Threshold to flag very scary scenarios
226
                /// Innoquous scenario illustrated here: https://github.com/Badger-Finance/ebtc-fuzz-review/issues/15
227
                if (newIcr - newIcrPrevious > NICR_ERROR_THRESHOLD) {
228
                    return false;
229
                }
230
            }
231
            newIcrPrevious = newIcr;
232

                            
                        
233
            currentCdp = sortedCdps.getNext(currentCdp);
234
        }
235
        return true;
236
    }
237

                            
                        
238
    function invariant_GENERAL_01(Vars memory vars) internal view returns (bool) {
239
        return !vars.isRecoveryModeBefore ? !vars.isRecoveryModeAfter : true;
240
    }
241

                            
                        
242
    function invariant_GENERAL_02(
243
        CdpManager cdpManager,
244
        PriceFeedTestnet priceFeedMock,
245
        EBTCToken eBTCToken
246
    ) internal view returns (bool) {
247
        // TODO how to calculate "the dollar value of eBTC"?
248
        // TODO how do we take into account underlying/shares into this calculation?
249
        return
250
            cdpManager.getTCR(priceFeedMock.getPrice()) > collateral.getPooledEthByShares(1e18)
251
                ? (cdpManager.getSystemCollShares() * priceFeedMock.getPrice()) / 1e18 >=
252
                    eBTCToken.totalSupply()
253
                : (cdpManager.getSystemCollShares() * priceFeedMock.getPrice()) / 1e18 <
254
                    eBTCToken.totalSupply();
255
    }
256

                            
                        
257
    function invariant_GENERAL_03(
258
        CdpManager cdpManager,
259
        BorrowerOperations borrowerOperations,
260
        EBTCToken eBTCToken,
261
        ICollateralToken collateral
262
    ) internal view returns (bool) {
263
        return
264
            collateral.balanceOf(address(cdpManager)) == 0 &&
265
            eBTCToken.balanceOf(address(cdpManager)) == 0 &&
266
            collateral.balanceOf(address(borrowerOperations)) == 0 &&
267
            eBTCToken.balanceOf(address(borrowerOperations)) == 0;
268
    }
269

                            
                        
270
    function invariant_GENERAL_05(
271
        ActivePool activePool,
272
        CdpManager cdpManager,
273
        ICollateralToken collateral
274
    ) internal view returns (bool) {
275
        uint256 totalStipendShares;
276

                            
                        
277
        // Iterate over CDPs add the stipendShares
278
        bytes32 currentCdp = sortedCdps.getFirst();
279
        while (currentCdp != bytes32(0)) {
280
            totalStipendShares += cdpManager.getCdpLiquidatorRewardShares(currentCdp);
281

                            
                        
282
            currentCdp = sortedCdps.getNext(currentCdp);
283
        }
284

                            
                        
285
        return
286
            collateral.sharesOf(address(activePool)) >=
287
            (activePool.getSystemCollShares() +
288
                activePool.getFeeRecipientClaimableCollShares() +
289
                totalStipendShares);
290
    }
291

                            
                        
292
    function invariant_GENERAL_05_B(
293
        CollSurplusPool surplusPool,
294
        ICollateralToken collateral
295
    ) internal view returns (bool) {
296
        return
297
            collateral.sharesOf(address(surplusPool)) >= (surplusPool.getTotalSurplusCollShares());
298
    }
299

                            
                        
300
    function invariant_GENERAL_06(
301
        EBTCToken eBTCToken,
302
        CdpManager cdpManager,
303
        SortedCdps sortedCdps
304
    ) internal view returns (bool) {
305
        uint256 totalSupply = eBTCToken.totalSupply();
306

                            
                        
307
        bytes32 currentCdp = sortedCdps.getFirst();
308
        uint256 cdpsBalance;
309
        while (currentCdp != bytes32(0)) {
310
            (uint256 entireDebt, , ) = cdpManager.getDebtAndCollShares(currentCdp);
311
            cdpsBalance += entireDebt;
312
            currentCdp = sortedCdps.getNext(currentCdp);
313
        }
314

                            
                        
315
        return totalSupply >= cdpsBalance;
316
    }
317

                            
                        
318
    function invariant_GENERAL_08(
319
        CdpManager cdpManager,
320
        SortedCdps sortedCdps,
321
        PriceFeedTestnet priceFeedTestnet,
322
        ICollateralToken collateral
323
    ) internal view returns (bool) {
324
        uint256 curentPrice = priceFeedTestnet.getPrice();
325

                            
                        
326
        bytes32 currentCdp = sortedCdps.getFirst();
327

                            
                        
328
        uint256 sumOfColl;
329
        uint256 sumOfDebt;
330
        while (currentCdp != bytes32(0)) {
331
            uint256 entireColl = cdpManager.getSyncedCdpCollShares(currentCdp);
332
            uint256 entireDebt = cdpManager.getSyncedCdpDebt(currentCdp);
333
            sumOfColl += entireColl;
334
            sumOfDebt += entireDebt;
335
            currentCdp = sortedCdps.getNext(currentCdp);
336
        }
337

                            
                        
338
        uint256 tcrFromSystem = cdpManager.getSyncedTCR(curentPrice);
339

                            
                        
340
        uint256 tcrFromSums = LiquityMath._computeCR(
341
            collateral.getPooledEthByShares(sumOfColl),
342
            sumOfDebt,
343
            curentPrice
344
        );
345
        /// @audit 1e8 precision
346
        return isApproximateEq(tcrFromSystem, tcrFromSums, 1e8); // Up to 1e8 precision is accepted
347
    }
348

                            
                        
349
    function invariant_GENERAL_09(
350
        CdpManager cdpManager,
351
        Vars memory vars
352
    ) internal view returns (bool) {
353
        if (vars.isRecoveryModeBefore) {
354
            if (vars.cdpDebtAfter > vars.cdpDebtBefore) return (vars.icrAfter > cdpManager.MCR());
355
            else return true;
356
        } else {
357
            return (vars.icrAfter > cdpManager.MCR());
358
        }
359
    }
360

                            
                        
361
    function invariant_GENERAL_12(
362
        CdpManager cdpManager,
363
        PriceFeedTestnet priceFeedMock,
364
        CRLens crLens
365
    ) internal returns (bool) {
366
        uint256 curentPrice = priceFeedMock.getPrice();
367
        return crLens.quoteRealTCR() == cdpManager.getSyncedTCR(curentPrice);
368
    }
369

                            
                        
370
    function invariant_GENERAL_13(
371
        CRLens crLens,
372
        CdpManager cdpManager,
373
        PriceFeedTestnet priceFeedMock,
374
        SortedCdps sortedCdps
375
    ) internal returns (bool) {
376
        bytes32 currentCdp = sortedCdps.getFirst();
377

                            
                        
378
        uint256 _price = priceFeedMock.getPrice();
379

                            
                        
380
        // Compare synched with quote for all Cdps
381
        while (currentCdp != bytes32(0)) {
382
            uint256 newIcr = crLens.quoteRealICR(currentCdp);
383
            uint256 synchedICR = cdpManager.getSyncedICR(currentCdp, _price);
384

                            
                        
385
            if (newIcr != synchedICR) {
386
                return false;
387
            }
388

                            
                        
389
            currentCdp = sortedCdps.getNext(currentCdp);
390
        }
391
        return true;
392
    }
393

                            
                        
394
    function invariant_GENERAL_14(
395
        CRLens crLens,
396
        CdpManager cdpManager,
397
        SortedCdps sortedCdps
398
    ) internal returns (bool) {
399
        bytes32 currentCdp = sortedCdps.getFirst();
400

                            
                        
401
        uint256 newIcrPrevious = type(uint256).max;
402

                            
                        
403
        // Compare synched with quote for all Cdps
404
        while (currentCdp != bytes32(0)) {
405
            uint256 newNICR = crLens.quoteRealNICR(currentCdp);
406
            uint256 synchedNICR = cdpManager.getSyncedNominalICR(currentCdp); // Uses cached stETH index -> It's not the "real NICR"
407

                            
                        
408
            if (newNICR != synchedNICR) {
409
                return false;
410
            }
411

                            
                        
412
            currentCdp = sortedCdps.getNext(currentCdp);
413
        }
414
        return true;
415
    }
416

                            
                        
417
    function invariant_LS_01(
418
        CdpManager cdpManager,
419
        LiquidationSequencer ls,
420
        SyncedLiquidationSequencer syncedLs,
421
        PriceFeedTestnet priceFeedTestnet
422
    ) internal returns (bool) {
423
        // Or just compare max lenght since that's the one with all of them
424
        uint256 n = cdpManager.getActiveCdpsCount();
425

                            
                        
426
        // Get
427
        uint256 price = priceFeedTestnet.getPrice();
428

                            
                        
429
        // Get lists
430
        bytes32[] memory cdpsFromCurrent = ls.sequenceLiqToBatchLiqWithPrice(n, price);
431
        bytes32[] memory cdpsSynced = syncedLs.sequenceLiqToBatchLiqWithPrice(n, price);
432

                            
                        
433
        uint256 length = cdpsFromCurrent.length;
434
        if (length != cdpsSynced.length) {
435
            return false;
436
        }
437

                            
                        
438
        // Compare Lists
439
        for (uint256 i; i < length; i++) {
440
            // Find difference = broken
441
            if (cdpsFromCurrent[i] != cdpsSynced[i]) {
442
                return false;
443
            }
444
        }
445

                            
                        
446
        // Implies we're good
447
        return true;
448
    }
449

                            
                        
450
    function invariant_DUMMY_01(PriceFeedTestnet priceFeedTestnet) internal view returns (bool) {
451
        return priceFeedTestnet.getPrice() > 0;
452
    }
453
}
454

                            
                        

Lines covered: 0 / 0 (0.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
abstract contract PropertiesDescriptions {
4
    ///////////////////////////////////////////////////////
5
    // Active Pool
6
    ///////////////////////////////////////////////////////
7

                            
                        
8
    string constant AP_01 =
9
        "AP-01: The collateral balance in the active pool is greater than or equal to its accounting number";
10
    string constant AP_02 =
11
        "AP-06: The collateral balance of the ActivePool is positive if there is at least one CDP open";
12
    string constant AP_03 =
13
        "AP-03: The eBTC debt accounting number in active pool is greater than or equal to its accounting number";
14
    string constant AP_04 =
15
        "AP-04: The total collateral in active pool should be equal to the sum of all individual CDP collateral";
16
    string constant AP_05 =
17
        "AP-05: The sum of debt accounting in active pool should be equal to sum of debt accounting of individual CDPs";
18

                            
                        
19
    ///////////////////////////////////////////////////////
20
    // Cdp Manager
21
    ///////////////////////////////////////////////////////
22

                            
                        
23
    string constant CDPM_01 =
24
        "CDPM-01: The count of active CDPs is equal to the SortedCdp list length";
25
    string constant CDPM_02 = "CDPM-02: The sum of active CDPs stake is equal to totalStakes";
26
    string constant CDPM_03 =
27
        "CDPM-03: The stFeePerUnit tracker for individual CDP is equal to or less than the global variable";
28
    string constant CDPM_04 = "CDPM-04: The total system value does not decrease during redemptions";
29
    string constant CDPM_05 = "CDPM-05: Redemptions do not increase the total system debt";
30
    string constant CDPM_06 = "CDPM-06: Redemptions do not increase the total system debt";
31

                            
                        
32
    ///////////////////////////////////////////////////////
33
    // Collateral Surplus Pool
34
    ///////////////////////////////////////////////////////
35

                            
                        
36
    string constant CSP_01 =
37
        "CSP-01: The collateral balance in the collSurplus pool is greater than or equal to its accounting number";
38

                            
                        
39
    ///////////////////////////////////////////////////////
40
    // Sorted List
41
    ///////////////////////////////////////////////////////
42

                            
                        
43
    string constant SL_01 =
44
        "SL-01: The NICR ranking in the sorted list should follow descending order";
45
    string constant SL_02 =
46
        "SL-02: The the first(highest) ICR in the sorted list should be greater or equal to TCR (with tolerance due to rounding errors)";
47
    string constant SL_03 = "SL-03: All CDPs have status active and stake greater than zero";
48
    string constant SL_05 =
49
        "SL-05: The CDPs should be sorted in descending order of new ICR (accrued)";
50

                            
                        
51
    ///////////////////////////////////////////////////////
52
    // Borrower Operations
53
    ///////////////////////////////////////////////////////
54

                            
                        
55
    string constant BO_01 = "BO-01: Users can only open CDPs with healthy ICR";
56
    string constant BO_02 = "BO-02: Users must repay all debt to close a CDP";
57
    string constant BO_03 = "BO-03: Adding collateral doesn't reduce Nominal ICR";
58
    string constant BO_04 = "BO-04: Removing collateral does not increase the Nominal ICR";
59
    string constant BO_05 =
60
        "BO-05: When a borrower closes their active CDP, the gas compensation is refunded to the user";
61
    string constant BO_07 = "BO-07: eBTC tokens are burned upon repayment of a CDP's debt";
62
    string constant BO_08 = "BO-08: TCR must increase after a repayment";
63

                            
                        
64
    ///////////////////////////////////////////////////////
65
    // General
66
    ///////////////////////////////////////////////////////
67
    string constant GENERAL_01 =
68
        "GENERAL-01: After any operation, the system should not enter in Recovery Mode";
69
    string constant GENERAL_02 =
70
        "GENERAL-02: The dollar value of the locked stETH exceeds the dollar value of the issued eBTC if TCR is greater than 100%";
71
    string constant GENERAL_03 =
72
        "GENERAL-03: CdpManager and BorrowerOperations do not hold value terms of stETH and eBTC unless there are donations";
73
    string constant GENERAL_05 =
74
        "GENERAL-05: At all times, the total stETH shares of the system exceeds the deposits if there is no negative rebasing events";
75
    string constant GENERAL_06 =
76
        "GENERAL-06: At all times, the total debt is greater than the sum of all debts from all CDPs";
77
    string constant GENERAL_08 =
78
        "GENERAL-08: At all times TCR = SUM(COLL)  * price / SUM(DEBT) of all CDPs";
79
    string constant GENERAL_09 =
80
        "GENERAL-09: After any operation, the ICR of a CDP must be above the MCR in Normal Mode, and after debt increase in Recovery Mode the ICR must be above the CCR";
81
    string constant GENERAL_10 = "GENERAL-10: All CDPs should maintain a minimum collateral size";
82
    string constant GENERAL_11 =
83
        "GENERAL-11: The TCR pre-computed (TCRNotified) is the same as the one after all calls";
84
    string constant GENERAL_12 =
85
        "GENERAL-12: The synchedTCR matches the TCR after accrual (as returned by CrLens)";
86
    string constant GENERAL_13 =
87
        "GENERAL-13: The SynchedICR of every CDP in the Linked List Matches the ICR the CDPs will have the call (as returned by CrLens)";
88
    string constant GENERAL_14 =
89
        "GENERAL-14: The NominalICR from `getNominalICR` matches `quoteRealNICR` (as returned by CrLens)";
90

                            
                        
91
    ///////////////////////////////////////////////////////
92
    // Redemptions
93
    ///////////////////////////////////////////////////////
94

                            
                        
95
    string constant R_07 = "R-07: TCR should not decrease after redemptions";
96
    string constant R_08 = "R-08: The user eBTC balance should be used to pay the system debt";
97

                            
                        
98
    ///////////////////////////////////////////////////////
99
    // Liquidations
100
    ///////////////////////////////////////////////////////
101

                            
                        
102
    string constant L_01 =
103
        "L-01: Liquidation only succeeds if ICR < 110% in Normal Mode, or if ICR < 125% in Recovery Mode";
104
    string constant L_09 =
105
        "L-09: Undercollateralized liquidations are also incentivized with the Gas Stipend";
106
    string constant L_12 = "L-12: TCR must increase after liquidation with no redistributions";
107
    string constant L_14 =
108
        "If the RM grace period is set and we're in recovery mode, new actions that keep the system in recovery mode should not change the cooldown timestamp";
109
    string constant L_15 =
110
        "L-15: The RM grace period should set if a BO/liquidation/redistribution makes the TCR above CCR";
111
    string constant L_16 =
112
        "L-16: The RM grace period should reset if a BO/liquidation/redistribution makes the TCR below CCR";
113

                            
                        
114
    ///////////////////////////////////////////////////////
115
    // eBTC
116
    ///////////////////////////////////////////////////////
117

                            
                        
118
    string constant EBTC_02 =
119
        "EBTC-02: Any eBTC holder (whether or not they have an active CDP) may redeem their eBTC unless TCR is below MCR";
120

                            
                        
121
    ///////////////////////////////////////////////////////
122
    // Fee Recipient
123
    ///////////////////////////////////////////////////////
124

                            
                        
125
    string constant F_01 = "F-01: `claimFeeRecipientCollShares` allows to claim at any time";
126
    string constant F_02 = "F-02: Fees From Redemptions are added to `claimFeeRecipientCollShares`";
127
    string constant F_03 = "F-03: Fees From FlashLoans are sent to the fee Recipient";
128
    ///////////////////////////////////////////////////////
129
    // Price Feed
130
    ///////////////////////////////////////////////////////
131

                            
                        
132
    string constant PF_01 = "PF-01: The price feed must never revert";
133
    string constant PF_02 = "PF-02: The price feed must follow valid status transitions";
134
    string constant PF_03 = "PF-03: The price feed must never deadlock";
135
    string constant PF_04 =
136
        "PF-04: The price feed should never report an outdated price if chainlink is Working";
137
    string constant PF_05 =
138
        "PF-05: The price feed should never use the fallback if chainlink is Working";
139
    string constant PF_06 = "PF-06: The system never tries to use the fallback if it is not set";
140
}
141

                            
                        

Lines covered: 199 / 199 (100.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import "@crytic/properties/contracts/util/PropertiesConstants.sol";
4

                            
                        
5
import "../../Interfaces/ICdpManagerData.sol";
6
import "../../Dependencies/SafeMath.sol";
7
import "../../CdpManager.sol";
8
import "../../LiquidationLibrary.sol";
9
import "../../BorrowerOperations.sol";
10
import "../../ActivePool.sol";
11
import "../../CollSurplusPool.sol";
12
import "../../SortedCdps.sol";
13
import "../../HintHelpers.sol";
14
import "../../FeeRecipient.sol";
15
import "../testnet/PriceFeedTestnet.sol";
16
import "../CollateralTokenTester.sol";
17
import "../EBTCTokenTester.sol";
18
import "../../Governor.sol";
19
import "../../EBTCDeployer.sol";
20

                            
                        
21
import "./Properties.sol";
22
import "./Actor.sol";
23
import "../BaseStorageVariables.sol";
24

                            
                        
25
abstract contract TargetContractSetup is BaseStorageVariables, PropertiesConstants {
26
    using SafeMath for uint;
27

                            
                        
28
    bytes4 internal constant BURN_SIG = bytes4(keccak256(bytes("burn(address,uint256)")));
29

                            
                        
30
    uint internal numberOfCdps;
31

                            
                        
32
    struct CDPChange {
33
        uint collAddition;
34
        uint collReduction;
35
        uint debtAddition;
36
        uint debtReduction;
37
    }
38

                            
                        
39
    function _setUp() internal {
40
        defaultGovernance = address(this);
41
        ebtcDeployer = new EBTCDeployer();
42

                            
                        
43
        // Default governance is deployer
44
        // vm.prank(defaultGovernance);
45
        collateral = new CollateralTokenTester();
46

                            
                        
47
        EBTCDeployer.EbtcAddresses memory addr = ebtcDeployer.getFutureEbtcAddresses();
48

                            
                        
49
        {
50
            bytes memory creationCode;
51
            bytes memory args;
52

                            
                        
53
            // Use EBTCDeployer to deploy all contracts at determistic addresses
54

                            
                        
55
            // Authority
56
            creationCode = type(Governor).creationCode;
57
            args = abi.encode(address(this));
58

                            
                        
59
            authority = Governor(
60
                ebtcDeployer.deploy(ebtcDeployer.AUTHORITY(), abi.encodePacked(creationCode, args))
61
            );
62

                            
                        
63
            // Liquidation Library
64
            creationCode = type(LiquidationLibrary).creationCode;
65
            args = abi.encode(
66
                addr.borrowerOperationsAddress,
67
                addr.collSurplusPoolAddress,
68
                addr.ebtcTokenAddress,
69
                addr.sortedCdpsAddress,
70
                addr.activePoolAddress,
71
                addr.priceFeedAddress,
72
                address(collateral)
73
            );
74

                            
                        
75
            liqudationLibrary = LiquidationLibrary(
76
                ebtcDeployer.deploy(
77
                    ebtcDeployer.LIQUIDATION_LIBRARY(),
78
                    abi.encodePacked(creationCode, args)
79
                )
80
            );
81

                            
                        
82
            // CDP Manager
83
            creationCode = type(CdpManager).creationCode;
84
            args = abi.encode(
85
                addr.liquidationLibraryAddress,
86
                addr.authorityAddress,
87
                addr.borrowerOperationsAddress,
88
                addr.collSurplusPoolAddress,
89
                addr.ebtcTokenAddress,
90
                addr.sortedCdpsAddress,
91
                addr.activePoolAddress,
92
                addr.priceFeedAddress,
93
                address(collateral)
94
            );
95

                            
                        
96
            cdpManager = CdpManager(
97
                ebtcDeployer.deploy(ebtcDeployer.CDP_MANAGER(), abi.encodePacked(creationCode, args))
98
            );
99

                            
                        
100
            // Borrower Operations
101
            creationCode = type(BorrowerOperations).creationCode;
102
            args = abi.encode(
103
                addr.cdpManagerAddress,
104
                addr.activePoolAddress,
105
                addr.collSurplusPoolAddress,
106
                addr.priceFeedAddress,
107
                addr.sortedCdpsAddress,
108
                addr.ebtcTokenAddress,
109
                addr.feeRecipientAddress,
110
                address(collateral)
111
            );
112

                            
                        
113
            borrowerOperations = BorrowerOperations(
114
                ebtcDeployer.deploy(
115
                    ebtcDeployer.BORROWER_OPERATIONS(),
116
                    abi.encodePacked(creationCode, args)
117
                )
118
            );
119

                            
                        
120
            // Price Feed Mock
121
            creationCode = type(PriceFeedTestnet).creationCode;
122
            args = abi.encode(addr.authorityAddress);
123

                            
                        
124
            priceFeedMock = PriceFeedTestnet(
125
                ebtcDeployer.deploy(ebtcDeployer.PRICE_FEED(), abi.encodePacked(creationCode, args))
126
            );
127

                            
                        
128
            // Sorted CDPS
129
            creationCode = type(SortedCdps).creationCode;
130
            args = abi.encode(
131
                type(uint256).max,
132
                addr.cdpManagerAddress,
133
                addr.borrowerOperationsAddress
134
            );
135

                            
                        
136
            sortedCdps = SortedCdps(
137
                ebtcDeployer.deploy(ebtcDeployer.SORTED_CDPS(), abi.encodePacked(creationCode, args))
138
            );
139

                            
                        
140
            // Active Pool
141
            creationCode = type(ActivePool).creationCode;
142
            args = abi.encode(
143
                addr.borrowerOperationsAddress,
144
                addr.cdpManagerAddress,
145
                address(collateral),
146
                addr.collSurplusPoolAddress,
147
                addr.feeRecipientAddress
148
            );
149

                            
                        
150
            activePool = ActivePool(
151
                ebtcDeployer.deploy(ebtcDeployer.ACTIVE_POOL(), abi.encodePacked(creationCode, args))
152
            );
153

                            
                        
154
            // Coll Surplus Pool
155
            creationCode = type(CollSurplusPool).creationCode;
156
            args = abi.encode(
157
                addr.borrowerOperationsAddress,
158
                addr.cdpManagerAddress,
159
                addr.activePoolAddress,
160
                address(collateral)
161
            );
162

                            
                        
163
            collSurplusPool = CollSurplusPool(
164
                ebtcDeployer.deploy(
165
                    ebtcDeployer.COLL_SURPLUS_POOL(),
166
                    abi.encodePacked(creationCode, args)
167
                )
168
            );
169

                            
                        
170
            // Hint Helpers
171
            creationCode = type(HintHelpers).creationCode;
172
            args = abi.encode(
173
                addr.sortedCdpsAddress,
174
                addr.cdpManagerAddress,
175
                address(collateral),
176
                addr.activePoolAddress,
177
                addr.priceFeedAddress
178
            );
179

                            
                        
180
            hintHelpers = HintHelpers(
181
                ebtcDeployer.deploy(
182
                    ebtcDeployer.HINT_HELPERS(),
183
                    abi.encodePacked(creationCode, args)
184
                )
185
            );
186

                            
                        
187
            // eBTC Token
188
            creationCode = type(EBTCTokenTester).creationCode;
189
            args = abi.encode(
190
                addr.cdpManagerAddress,
191
                addr.borrowerOperationsAddress,
192
                addr.authorityAddress
193
            );
194

                            
                        
195
            eBTCToken = EBTCTokenTester(
196
                ebtcDeployer.deploy(ebtcDeployer.EBTC_TOKEN(), abi.encodePacked(creationCode, args))
197
            );
198

                            
                        
199
            // Fee Recipieint
200
            creationCode = type(FeeRecipient).creationCode;
201
            args = abi.encode(
202
                addr.ebtcTokenAddress,
203
                addr.cdpManagerAddress,
204
                addr.borrowerOperationsAddress,
205
                addr.activePoolAddress,
206
                address(collateral)
207
            );
208

                            
                        
209
            feeRecipient = FeeRecipient(
210
                ebtcDeployer.deploy(
211
                    ebtcDeployer.FEE_RECIPIENT(),
212
                    abi.encodePacked(creationCode, args)
213
                )
214
            );
215

                            
                        
216
            // Configure authority
217
            authority.setRoleName(0, "Admin");
218
            authority.setRoleName(1, "eBTCToken: mint");
219
            authority.setRoleName(2, "eBTCToken: burn");
220
            authority.setRoleName(3, "CDPManager: all");
221
            authority.setRoleName(4, "PriceFeed: setTellorCaller");
222
            authority.setRoleName(5, "BorrowerOperations: all");
223

                            
                        
224
            authority.setRoleCapability(1, address(eBTCToken), eBTCToken.mint.selector, true);
225

                            
                        
226
            authority.setRoleCapability(2, address(eBTCToken), BURN_SIG, true);
227

                            
                        
228
            authority.setRoleCapability(
229
                3,
230
                address(cdpManager),
231
                cdpManager.setStakingRewardSplit.selector,
232
                true
233
            );
234
            authority.setRoleCapability(
235
                3,
236
                address(cdpManager),
237
                cdpManager.setRedemptionFeeFloor.selector,
238
                true
239
            );
240
            authority.setRoleCapability(
241
                3,
242
                address(cdpManager),
243
                cdpManager.setMinuteDecayFactor.selector,
244
                true
245
            );
246
            authority.setRoleCapability(3, address(cdpManager), cdpManager.setBeta.selector, true);
247
            authority.setRoleCapability(
248
                3,
249
                address(cdpManager),
250
                cdpManager.setGracePeriod.selector,
251
                true
252
            );
253
            authority.setRoleCapability(
254
                3,
255
                address(cdpManager),
256
                cdpManager.setRedemptionsPaused.selector,
257
                true
258
            );
259

                            
                        
260
            authority.setRoleCapability(
261
                4,
262
                address(priceFeedMock),
263
                priceFeedMock.setFallbackCaller.selector,
264
                true
265
            );
266

                            
                        
267
            authority.setRoleCapability(
268
                5,
269
                address(borrowerOperations),
270
                borrowerOperations.setFeeBps.selector,
271
                true
272
            );
273
            authority.setRoleCapability(
274
                5,
275
                address(borrowerOperations),
276
                borrowerOperations.setFlashLoansPaused.selector,
277
                true
278
            );
279

                            
                        
280
            authority.setRoleCapability(5, address(activePool), activePool.setFeeBps.selector, true);
281
            authority.setRoleCapability(
282
                5,
283
                address(activePool),
284
                activePool.setFlashLoansPaused.selector,
285
                true
286
            );
287
            authority.setRoleCapability(
288
                5,
289
                address(activePool),
290
                activePool.claimFeeRecipientCollShares.selector,
291
                true
292
            );
293

                            
                        
294
            authority.setUserRole(defaultGovernance, 0, true);
295
            authority.setUserRole(defaultGovernance, 1, true);
296
            authority.setUserRole(defaultGovernance, 2, true);
297
            authority.setUserRole(defaultGovernance, 3, true);
298
            authority.setUserRole(defaultGovernance, 4, true);
299
            authority.setUserRole(defaultGovernance, 5, true);
300

                            
                        
301
            crLens = new CRLens(address(cdpManager), address(priceFeedMock));
302

                            
                        
303
            liquidationSequencer = new LiquidationSequencer(
304
                address(cdpManager),
305
                address(cdpManager.sortedCdps()),
306
                address(priceFeedMock),
307
                address(activePool),
308
                address(collateral)
309
            );
310
            syncedLiquidationSequencer = new SyncedLiquidationSequencer(
311
                address(cdpManager),
312
                address(cdpManager.sortedCdps()),
313
                address(priceFeedMock),
314
                address(activePool),
315
                address(collateral)
316
            );
317
        }
318
    }
319

                            
                        
320
    event Log(string);
321

                            
                        
322
    function _setUpFork() internal {
323
        defaultGovernance = address(0xA967Ba66Fb284EC18bbe59f65bcf42dD11BA8128);
324
        ebtcDeployer = EBTCDeployer(0xe90f99c08F286c48db4D1AfdAE6C122de69B7219);
325
        collateral = CollateralTokenTester(payable(0xf8017430A0efE03577f6aF88069a21900448A373));
326
        {
327
            authority = Governor(0x4945Fc25282b1bC103d2C62C251Cd022138c1de9);
328
            liqudationLibrary = LiquidationLibrary(0xE8943a17363DE9A6e0d4A5d48d5Ab45283199F77);
329
            cdpManager = CdpManager(0x0c5C2B93b96C9B3aD7fb9915952BD7BA256C4f04);
330
            borrowerOperations = BorrowerOperations(0xA178BFBc42E3D886d540CDDcf4562c53a8Fc02c1);
331
            priceFeedMock = PriceFeedTestnet(0x5C819E5D61EFCfBd7e4635f1112f3bF94663999b);
332
            sortedCdps = SortedCdps(0xDeFF25eC3cd3041BC8B9A464F9BEc12EB8247Be6);
333
            activePool = ActivePool(0x55abdfb760dd032627D531f7cF3DAa72549CEbA2);
334
            collSurplusPool = CollSurplusPool(0x7b4D951D7b8090f62bD009b371abd7Fe04aB7e1A);
335
            hintHelpers = HintHelpers(0xCaBdBc4218dd4b9E3fB9842232aD0aFc7c431693);
336
            eBTCToken = EBTCTokenTester(0x9Aa69Db8c53E504EF22615390EE9Eb72cb8bE498);
337
            feeRecipient = FeeRecipient(0x40FF68eaE525233950B63C2BCEa39770efDE52A4);
338

                            
                        
339
            crLens = new CRLens(address(cdpManager), address(priceFeedMock));
340

                            
                        
341
            liquidationSequencer = new LiquidationSequencer(
342
                address(cdpManager),
343
                address(cdpManager.sortedCdps()),
344
                address(priceFeedMock),
345
                address(activePool),
346
                address(collateral)
347
            );
348
        }
349
    }
350

                            
                        
351
    function _setUpActors() internal {
352
        bool success;
353
        address[] memory tokens = new address[](2);
354
        tokens[0] = address(eBTCToken);
355
        tokens[1] = address(collateral);
356
        address[] memory callers = new address[](2);
357
        callers[0] = address(borrowerOperations);
358
        callers[1] = address(activePool);
359
        address[] memory addresses = new address[](3);
360
        addresses[0] = USER1;
361
        addresses[1] = USER2;
362
        addresses[2] = USER3;
363
        for (uint i = 0; i < NUMBER_OF_ACTORS; i++) {
364
            actors[addresses[i]] = new Actor(tokens, callers);
365
            (success, ) = address(actors[addresses[i]]).call{value: INITIAL_ETH_BALANCE}("");
366
            assert(success);
367
            (success, ) = actors[addresses[i]].proxy(
368
                address(collateral),
369
                abi.encodeWithSelector(CollateralTokenTester.deposit.selector, ""),
370
                INITIAL_COLL_BALANCE
371
            );
372
            assert(success);
373
        }
374
    }
375
}
376

                            
                        

Lines covered: 524 / 593 (88.4%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "@crytic/properties/contracts/util/Hevm.sol";
6

                            
                        
7
import "../../Interfaces/ICdpManagerData.sol";
8
import "../../Dependencies/SafeMath.sol";
9
import "../../CdpManager.sol";
10
import "../../LiquidationLibrary.sol";
11
import "../../BorrowerOperations.sol";
12
import "../../ActivePool.sol";
13
import "../../CollSurplusPool.sol";
14
import "../../SortedCdps.sol";
15
import "../../HintHelpers.sol";
16
import "../../FeeRecipient.sol";
17
import "../testnet/PriceFeedTestnet.sol";
18
import "../CollateralTokenTester.sol";
19
import "../EBTCTokenTester.sol";
20
import "../../Governor.sol";
21
import "../../EBTCDeployer.sol";
22

                            
                        
23
import "./Properties.sol";
24
import "./Actor.sol";
25
import "./BeforeAfter.sol";
26
import "./TargetContractSetup.sol";
27
import "./Asserts.sol";
28
import "../BaseStorageVariables.sol";
29

                            
                        
30
abstract contract TargetFunctions is Properties {
31
    modifier setup() virtual {
32
        actor = actors[msg.sender];
33
        _;
34
    }
35

                            
                        
36
    ///////////////////////////////////////////////////////
37
    // Helper functions
38
    ///////////////////////////////////////////////////////
39

                            
                        
40
    function _totalCdpsBelowMcr() internal returns (uint256) {
41
        uint256 ans;
42
        bytes32 currentCdp = sortedCdps.getFirst();
43

                            
                        
44
        uint256 _price = priceFeedMock.getPrice();
45

                            
                        
46
        while (currentCdp != bytes32(0)) {
47
            if (cdpManager.getICR(currentCdp, _price) < cdpManager.MCR()) {
48
                ++ans;
49
            }
50

                            
                        
51
            currentCdp = sortedCdps.getNext(currentCdp);
52
        }
53

                            
                        
54
        return ans;
55
    }
56

                            
                        
57
    function _getCdpIdsAndICRs() internal view returns (Cdp[] memory ans) {
58
        ans = new Cdp[](sortedCdps.getSize());
59
        uint256 i = 0;
60
        bytes32 currentCdp = sortedCdps.getFirst();
61

                            
                        
62
        uint256 _price = priceFeedMock.getPrice();
63

                            
                        
64
        while (currentCdp != bytes32(0)) {
65
            ans[i++] = Cdp({id: currentCdp, icr: cdpManager.getSyncedICR(currentCdp, _price)}); /// @audit NOTE: Synced to ensure it's realistic
66

                            
                        
67
            currentCdp = sortedCdps.getNext(currentCdp);
68
        }
69
    }
70

                            
                        
71
    function _cdpIdsAndICRsDiff(
72
        Cdp[] memory superset,
73
        Cdp[] memory subset
74
    ) internal returns (Cdp[] memory ans) {
75
        ans = new Cdp[](superset.length - subset.length);
76
        uint256 index = 0;
77
        for (uint256 i = 0; i < superset.length; i++) {
78
            bool duplicate = false;
79
            for (uint256 j = 0; j < subset.length; j++) {
80
                if (superset[i].id == subset[j].id) {
81
                    duplicate = true;
82
                }
83
            }
84
            if (!duplicate) {
85
                ans[index++] = superset[i];
86
            }
87
        }
88
    }
89

                            
                        
90
    function _getRandomCdp(uint _i) internal view returns (bytes32) {
91
        uint _cdpIdx = _i % cdpManager.getActiveCdpsCount();
92
        return cdpManager.CdpIds(_cdpIdx);
93
    }
94

                            
                        
95
    event FlashLoanAction(uint, uint);
96

                            
                        
97
    function _getFlashLoanActions(uint256 value) internal returns (bytes memory) {
98
        uint256 _actions = between(value, 1, MAX_FLASHLOAN_ACTIONS);
99
        uint256 _EBTCAmount = between(value, 1, eBTCToken.totalSupply() / 2);
100
        uint256 _col = between(value, 1, cdpManager.getSystemCollShares() / 2);
101
        uint256 _n = between(value, 1, cdpManager.getActiveCdpsCount());
102

                            
                        
103
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
104
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
105
        uint256 _i = between(value, 0, numberOfCdps - 1);
106
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
107
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
108

                            
                        
109
        address[] memory _targets = new address[](_actions);
110
        bytes[] memory _calldatas = new bytes[](_actions);
111

                            
                        
112
        address[] memory _allTargets = new address[](6);
113
        bytes[] memory _allCalldatas = new bytes[](6);
114

                            
                        
115
        _allTargets[0] = address(borrowerOperations);
116
        _allCalldatas[0] = abi.encodeWithSelector(
117
            BorrowerOperations.openCdp.selector,
118
            _EBTCAmount,
119
            bytes32(0),
120
            bytes32(0),
121
            _col
122
        );
123

                            
                        
124
        _allTargets[1] = address(borrowerOperations);
125
        _allCalldatas[1] = abi.encodeWithSelector(BorrowerOperations.closeCdp.selector, _cdpId);
126

                            
                        
127
        _allTargets[2] = address(borrowerOperations);
128
        _allCalldatas[2] = abi.encodeWithSelector(
129
            BorrowerOperations.addColl.selector,
130
            _cdpId,
131
            _cdpId,
132
            _cdpId,
133
            _col
134
        );
135

                            
                        
136
        _allTargets[3] = address(borrowerOperations);
137
        _allCalldatas[3] = abi.encodeWithSelector(
138
            BorrowerOperations.withdrawColl.selector,
139
            _cdpId,
140
            _col,
141
            _cdpId,
142
            _cdpId
143
        );
144

                            
                        
145
        _allTargets[4] = address(borrowerOperations);
146
        _allCalldatas[4] = abi.encodeWithSelector(
147
            BorrowerOperations.withdrawEBTC.selector,
148
            _cdpId,
149
            _EBTCAmount,
150
            _cdpId,
151
            _cdpId
152
        );
153

                            
                        
154
        _allTargets[5] = address(borrowerOperations);
155
        _allCalldatas[5] = abi.encodeWithSelector(
156
            BorrowerOperations.repayEBTC.selector,
157
            _cdpId,
158
            _EBTCAmount,
159
            _cdpId,
160
            _cdpId
161
        );
162

                            
                        
163
        for (uint256 _j = 0; _j < _actions; ++_j) {
164
            _i = uint256(keccak256(abi.encodePacked(value, _j, _i))) % _allTargets.length;
165
            emit FlashLoanAction(_j, _i);
166

                            
                        
167
            _targets[_j] = _allTargets[_i];
168
            _calldatas[_j] = _allCalldatas[_i];
169
        }
170

                            
                        
171
        return abi.encode(_targets, _calldatas);
172
    }
173

                            
                        
174
    function _getFirstCdpWithIcrGteMcr() internal returns (bytes32) {
175
        bytes32 _cId = sortedCdps.getLast();
176
        address currentBorrower = sortedCdps.getOwnerAddress(_cId);
177
        // Find the first cdp with ICR >= MCR
178
        while (
179
            currentBorrower != address(0) &&
180
            cdpManager.getICR(_cId, priceFeedMock.getPrice()) < cdpManager.MCR()
181
        ) {
182
            _cId = sortedCdps.getPrev(_cId);
183
            currentBorrower = sortedCdps.getOwnerAddress(_cId);
184
        }
185
        return _cId;
186
    }
187

                            
                        
188
    function _atLeastOneCdpIsLiquidatable(
189
        Cdp[] memory cdps,
190
        bool isRecoveryModeBefore
191
    ) internal view returns (bool atLeastOneCdpIsLiquidatable) {
192
        for (uint256 i = 0; i < cdps.length; ++i) {
193
            if (
194
                cdps[i].icr < cdpManager.MCR() ||
195
                (cdps[i].icr < cdpManager.CCR() && isRecoveryModeBefore)
196
            ) {
197
                atLeastOneCdpIsLiquidatable = true;
198
                break;
199
            }
200
        }
201
    }
202

                            
                        
203
    ///////////////////////////////////////////////////////
204
    // CdpManager
205
    ///////////////////////////////////////////////////////
206

                            
                        
207
    function liquidate(uint _i) public setup {
208
        bool success;
209
        bytes memory returnData;
210

                            
                        
211
        require(cdpManager.getActiveCdpsCount() > 1, "Cannot liquidate last CDP");
212

                            
                        
213
        bytes32 _cdpId = _getRandomCdp(_i);
214

                            
                        
215
        (uint256 entireDebt, , ) = cdpManager.getDebtAndCollShares(_cdpId);
216
        require(entireDebt > 0, "CDP must have debt");
217

                            
                        
218
        _before(_cdpId);
219

                            
                        
220
        (success, returnData) = actor.proxy(
221
            address(cdpManager),
222
            abi.encodeWithSelector(CdpManager.liquidate.selector, _cdpId)
223
        );
224

                            
                        
225
        _after(_cdpId);
226

                            
                        
227
        if (success) {
228
            if (
229
                vars.newIcrBefore >= cdpManager.LICR() // 103% else liquidating locks in bad debt
230
            ) {
231
                // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/5
232
                gte(vars.newTcrAfter, vars.newTcrBefore, L_12);
233
            }
234
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/12
235
            t(
236
                vars.newIcrBefore < cdpManager.MCR() ||
237
                    (vars.newIcrBefore < cdpManager.CCR() && vars.isRecoveryModeBefore),
238
                L_01
239
            );
240
            if (
241
                vars.lastGracePeriodStartTimestampIsSetBefore &&
242
                vars.isRecoveryModeBefore &&
243
                vars.isRecoveryModeAfter
244
            ) {
245
                eq(
246
                    vars.lastGracePeriodStartTimestampBefore,
247
                    vars.lastGracePeriodStartTimestampAfter,
248
                    L_14
249
                );
250
            }
251

                            
                        
252
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
253
                t(
254
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
255
                        vars.lastGracePeriodStartTimestampIsSetAfter,
256
                    L_15
257
                );
258
            }
259

                            
                        
260
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
261
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
262
            }
263

                            
                        
264
            gte(
265
                vars.actorCollAfter,
266
                vars.actorCollBefore +
267
                    collateral.getPooledEthByShares(vars.liquidatorRewardSharesBefore),
268
                L_09
269
            );
270
        } else if (vars.sortedCdpsSizeBefore > _i) {
271
            assertRevertReasonNotEqual(returnData, "Panic(17)");
272
        }
273
    }
274

                            
                        
275
    function partialLiquidate(uint _i, uint _partialAmount) public setup {
276
        bool success;
277
        bytes memory returnData;
278

                            
                        
279
        require(cdpManager.getActiveCdpsCount() > 1, "Cannot liquidate last CDP");
280

                            
                        
281
        bytes32 _cdpId = _getRandomCdp(_i);
282

                            
                        
283
        (uint256 entireDebt, , ) = cdpManager.getDebtAndCollShares(_cdpId);
284
        require(entireDebt > 0, "CDP must have debt");
285

                            
                        
286
        _partialAmount = between(_partialAmount, 0, entireDebt);
287

                            
                        
288
        _before(_cdpId);
289

                            
                        
290
        (success, returnData) = actor.proxy(
291
            address(cdpManager),
292
            abi.encodeWithSelector(
293
                CdpManager.partiallyLiquidate.selector,
294
                _cdpId,
295
                _partialAmount,
296
                _cdpId,
297
                _cdpId
298
            )
299
        );
300

                            
                        
301
        _after(_cdpId);
302

                            
                        
303
        if (success) {
304
            lt(vars.cdpDebtAfter, vars.cdpDebtBefore, "Partial liquidation must reduce CDP debt");
305

                            
                        
306
            if (
307
                vars.newIcrBefore >= cdpManager.LICR() // 103% else liquidating locks in bad debt
308
            ) {
309
                // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/5
310
                gte(vars.newTcrAfter, vars.newTcrBefore, L_12);
311
            }
312
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/12
313
            t(
314
                vars.newIcrBefore < cdpManager.MCR() ||
315
                    (vars.newIcrBefore < cdpManager.CCR() && vars.isRecoveryModeBefore),
316
                L_01
317
            );
318

                            
                        
319
            eq(vars.sortedCdpsSizeAfter, vars.sortedCdpsSizeBefore, "Partial Liquidation CANNOT close CDPs");
320

                            
                        
321
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
322
            if (vars.sortedCdpsSizeAfter == vars.sortedCdpsSizeBefore) {
323
                // CDP was not fully liquidated
324
                gte(
325
                    collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
326
                    borrowerOperations.MIN_NET_COLL(),
327
                    GENERAL_10
328
                );
329
            }
330

                            
                        
331
            if (
332
                vars.lastGracePeriodStartTimestampIsSetBefore &&
333
                vars.isRecoveryModeBefore &&
334
                vars.isRecoveryModeAfter
335
            ) {
336
                eq(
337
                    vars.lastGracePeriodStartTimestampBefore,
338
                    vars.lastGracePeriodStartTimestampAfter,
339
                    L_14
340
                );
341
            }
342

                            
                        
343
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
344
                t(
345
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
346
                        vars.lastGracePeriodStartTimestampIsSetAfter,
347
                    L_15
348
                );
349
            }
350

                            
                        
351
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
352
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
353
            }
354
        } else {
355
            assertRevertReasonNotEqual(returnData, "Panic(17)");
356
        }
357
    }
358

                            
                        
359
    function liquidateCdps(uint _n) public setup {
360
        bool success;
361
        bytes memory returnData;
362

                            
                        
363
        require(cdpManager.getActiveCdpsCount() > 1, "Cannot liquidate last CDP");
364

                            
                        
365
        _n = between(_n, 1, cdpManager.getActiveCdpsCount());
366

                            
                        
367
        Cdp[] memory cdpsBefore = _getCdpIdsAndICRs();
368

                            
                        
369
        _before(bytes32(0));
370

                            
                        
371
        bytes32[] memory batch = liquidationSequencer.sequenceLiqToBatchLiqWithPrice(
372
            _n,
373
            vars.priceBefore
374
        );
375

                            
                        
376
        (success, returnData) = actor.proxy(
377
            address(cdpManager),
378
            abi.encodeWithSelector(CdpManager.batchLiquidateCdps.selector, batch)
379
        );
380

                            
                        
381
        _after(bytes32(0));
382

                            
                        
383
        if (success) {
384
            Cdp[] memory cdpsAfter = _getCdpIdsAndICRs();
385

                            
                        
386
            Cdp[] memory cdpsLiquidated = _cdpIdsAndICRsDiff(cdpsBefore, cdpsAfter);
387
            gte(
388
                cdpsLiquidated.length,
389
                1,
390
                "liquidateCdps must liquidate at least 1 CDP when successful"
391
            );
392
            lte(cdpsLiquidated.length, _n, "liquidateCdps must not liquidate more than n CDPs");
393
            for (uint256 i = 0; i < cdpsLiquidated.length; ++i) {
394
                // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/12
395
                t(
396
                    cdpsLiquidated[i].icr < cdpManager.MCR() ||
397
                        (cdpsLiquidated[i].icr < cdpManager.CCR() && vars.isRecoveryModeBefore),
398
                    L_01
399
                );
400
            }
401

                            
                        
402
            if (
403
                vars.lastGracePeriodStartTimestampIsSetBefore &&
404
                vars.isRecoveryModeBefore &&
405
                vars.isRecoveryModeAfter
406
            ) {
407
                eq(
408
                    vars.lastGracePeriodStartTimestampBefore,
409
                    vars.lastGracePeriodStartTimestampAfter,
410
                    L_14
411
                );
412
            }
413

                            
                        
414
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
415
                t(
416
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
417
                        vars.lastGracePeriodStartTimestampIsSetAfter,
418
                    L_15
419
                );
420
            }
421

                            
                        
422
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
423
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
424
            }
425
        } else if (vars.sortedCdpsSizeBefore > _n) {
426
            if (_atLeastOneCdpIsLiquidatable(cdpsBefore, vars.isRecoveryModeBefore)) {
427
                assertRevertReasonNotEqual(returnData, "Panic(17)");
428
            }
429
        }
430
    }
431

                            
                        
432
    function redeemCollateral(
433
        uint _EBTCAmount,
434
        uint _partialRedemptionHintNICR,
435
        uint _maxFeePercentage,
436
        uint _maxIterations
437
    ) public setup {
438
        bool success;
439
        bytes memory returnData;
440

                            
                        
441
        _EBTCAmount = between(_EBTCAmount, 0, eBTCToken.balanceOf(address(actor)));
442
        _maxIterations = between(_maxIterations, 0, 1);
443

                            
                        
444
        _maxFeePercentage = between(
445
            _maxFeePercentage,
446
            cdpManager.redemptionFeeFloor(),
447
            cdpManager.DECIMAL_PRECISION()
448
        );
449

                            
                        
450
        bytes32 _cdpId = _getFirstCdpWithIcrGteMcr();
451

                            
                        
452
        _before(_cdpId);
453

                            
                        
454
        (success, returnData) = actor.proxy(
455
            address(cdpManager),
456
            abi.encodeWithSelector(
457
                CdpManager.redeemCollateral.selector,
458
                _EBTCAmount,
459
                bytes32(0),
460
                bytes32(0),
461
                bytes32(0),
462
                _partialRedemptionHintNICR,
463
                _maxIterations,
464
                _maxFeePercentage
465
            )
466
        );
467

                            
                        
468
        require(success);
469

                            
                        
470
        _after(_cdpId);
471

                            
                        
472
        gt(vars.tcrBefore, cdpManager.MCR(), EBTC_02);
473
        if (_maxIterations == 1) {
474
            gte(vars.activePoolDebtBefore, vars.activePoolDebtAfter, CDPM_05);
475
            gte(vars.cdpDebtBefore, vars.cdpDebtAfter, CDPM_06);
476
            // TODO: CHECK THIS
477
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/10#issuecomment-1702685732
478
            if (vars.sortedCdpsSizeBefore == vars.sortedCdpsSizeAfter) {
479
                // Redemptions do not reduce TCR
480
                // If redemptions do not close any CDP that was healthy (low debt, high coll)
481
                gt(
482
                    vars.newTcrAfter, // TODO: See how this breaks
483
                    vars.newTcrBefore,
484
                    R_07
485
                );
486
            }
487
            t(invariant_CDPM_04(vars), CDPM_04);
488
        }
489
        gt(vars.actorEbtcBefore, vars.actorEbtcAfter, R_08);
490

                            
                        
491
        // Verify Fee Recipient Received the Fee
492
        gte(vars.feeRecipientTotalCollAfter, vars.feeRecipientTotalCollBefore, F_02);
493

                            
                        
494
        if (
495
            vars.lastGracePeriodStartTimestampIsSetBefore &&
496
            vars.isRecoveryModeBefore &&
497
            vars.isRecoveryModeAfter
498
        ) {
499
            eq(
500
                vars.lastGracePeriodStartTimestampBefore,
501
                vars.lastGracePeriodStartTimestampAfter,
502
                L_14
503
            );
504
        }
505

                            
                        
506
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
507
            t(
508
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
509
                    vars.lastGracePeriodStartTimestampIsSetAfter,
510
                L_15
511
            );
512
        }
513

                            
                        
514
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
515
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
516
        }
517
    }
518

                            
                        
519
    ///////////////////////////////////////////////////////
520
    // ActivePool
521
    ///////////////////////////////////////////////////////
522

                            
                        
523
    function flashLoanColl(uint _amount) internal setup {
524
        bool success;
525
        bytes memory returnData;
526

                            
                        
527
        _amount = between(_amount, 0, activePool.maxFlashLoan(address(collateral)));
528
        uint _fee = activePool.flashFee(address(collateral), _amount);
529

                            
                        
530
        _before(bytes32(0));
531

                            
                        
532
        // take the flashloan which should always cost the fee paid by caller
533
        uint _balBefore = collateral.balanceOf(activePool.feeRecipientAddress());
534
        (success, returnData) = actor.proxy(
535
            address(activePool),
536
            abi.encodeWithSelector(
537
                ActivePool.flashLoan.selector,
538
                IERC3156FlashBorrower(address(actor)),
539
                address(collateral),
540
                _amount,
541
                _getFlashLoanActions(_amount)
542
            )
543
        );
544

                            
                        
545
        require(success);
546

                            
                        
547
        _after(bytes32(0));
548

                            
                        
549
        uint _balAfter = collateral.balanceOf(activePool.feeRecipientAddress());
550
        eq(_balAfter - _balBefore, _fee, F_03);
551

                            
                        
552
        if (
553
            vars.lastGracePeriodStartTimestampIsSetBefore &&
554
            vars.isRecoveryModeBefore &&
555
            vars.isRecoveryModeAfter
556
        ) {
557
            eq(
558
                vars.lastGracePeriodStartTimestampBefore,
559
                vars.lastGracePeriodStartTimestampAfter,
560
                L_14
561
            );
562
        }
563

                            
                        
564
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
565
            t(
566
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
567
                    vars.lastGracePeriodStartTimestampIsSetAfter,
568
                L_15
569
            );
570
        }
571

                            
                        
572
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
573
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
574
        }
575
    }
576

                            
                        
577
    ///////////////////////////////////////////////////////
578
    // BorrowerOperations
579
    ///////////////////////////////////////////////////////
580

                            
                        
581
    function flashLoanEBTC(uint _amount) internal setup {
582
        bool success;
583
        bytes memory returnData;
584

                            
                        
585
        _amount = between(_amount, 0, borrowerOperations.maxFlashLoan(address(eBTCToken)));
586

                            
                        
587
        uint _fee = borrowerOperations.flashFee(address(eBTCToken), _amount);
588

                            
                        
589
        _before(bytes32(0));
590

                            
                        
591
        // take the flashloan which should always cost the fee paid by caller
592
        uint _balBefore = eBTCToken.balanceOf(borrowerOperations.feeRecipientAddress());
593
        (success, returnData) = actor.proxy(
594
            address(borrowerOperations),
595
            abi.encodeWithSelector(
596
                BorrowerOperations.flashLoan.selector,
597
                IERC3156FlashBorrower(address(actor)),
598
                address(eBTCToken),
599
                _amount,
600
                _getFlashLoanActions(_amount)
601
            )
602
        );
603

                            
                        
604
        // BorrowerOperations.flashLoan may revert due to reentrancy
605
        require(success);
606

                            
                        
607
        _after(bytes32(0));
608

                            
                        
609
        uint _balAfter = eBTCToken.balanceOf(borrowerOperations.feeRecipientAddress());
610
        eq(_balAfter - _balBefore, _fee, F_03);
611

                            
                        
612
        if (
613
            vars.lastGracePeriodStartTimestampIsSetBefore &&
614
            vars.isRecoveryModeBefore &&
615
            vars.isRecoveryModeAfter
616
        ) {
617
            eq(
618
                vars.lastGracePeriodStartTimestampBefore,
619
                vars.lastGracePeriodStartTimestampAfter,
620
                L_14
621
            );
622
        }
623

                            
                        
624
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
625
            t(
626
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
627
                    vars.lastGracePeriodStartTimestampIsSetAfter,
628
                L_15
629
            );
630
        }
631

                            
                        
632
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
633
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
634
        }
635
    }
636

                            
                        
637
    function openCdp(uint256 _col, uint256 _EBTCAmount) public setup returns (bytes32 _cdpId) {
638
        bool success;
639
        bytes memory returnData;
640

                            
                        
641
        // we pass in CCR instead of MCR in case it's the first one
642
        uint price = priceFeedMock.getPrice();
643

                            
                        
644
        uint256 requiredCollAmount = (_EBTCAmount * cdpManager.CCR()) / (price);
645
        uint256 minCollAmount = max(
646
            cdpManager.MIN_NET_COLL() + borrowerOperations.LIQUIDATOR_REWARD(),
647
            requiredCollAmount
648
        );
649
        uint256 maxCollAmount = min(2 * minCollAmount, INITIAL_COLL_BALANCE / 10);
650
        _col = between(requiredCollAmount, minCollAmount, maxCollAmount);
651

                            
                        
652
        (success, ) = actor.proxy(
653
            address(collateral),
654
            abi.encodeWithSelector(
655
                CollateralTokenTester.approve.selector,
656
                address(borrowerOperations),
657
                _col
658
            )
659
        );
660
        t(success, "Approve never fails");
661

                            
                        
662
        _before(bytes32(0));
663

                            
                        
664
        (success, returnData) = actor.proxy(
665
            address(borrowerOperations),
666
            abi.encodeWithSelector(
667
                BorrowerOperations.openCdp.selector,
668
                _EBTCAmount,
669
                bytes32(0),
670
                bytes32(0),
671
                _col
672
            )
673
        );
674

                            
                        
675
        if (success) {
676
            _cdpId = abi.decode(returnData, (bytes32));
677
            _after(_cdpId);
678

                            
                        
679
            t(invariant_GENERAL_01(vars), GENERAL_01);
680
            gt(vars.icrAfter, cdpManager.MCR(), BO_01);
681

                            
                        
682
            eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
683

                            
                        
684
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
685
            t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
686
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
687
            gte(
688
                collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
689
                borrowerOperations.MIN_NET_COLL(),
690
                GENERAL_10
691
            );
692
            eq(
693
                vars.sortedCdpsSizeBefore + 1,
694
                vars.sortedCdpsSizeAfter,
695
                "CDPs count must have increased"
696
            );
697
            if (
698
                vars.lastGracePeriodStartTimestampIsSetBefore &&
699
                vars.isRecoveryModeBefore &&
700
                vars.isRecoveryModeAfter
701
            ) {
702
                eq(
703
                    vars.lastGracePeriodStartTimestampBefore,
704
                    vars.lastGracePeriodStartTimestampAfter,
705
                    L_14
706
                );
707
            }
708

                            
                        
709
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
710
                t(
711
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
712
                        vars.lastGracePeriodStartTimestampIsSetAfter,
713
                    L_15
714
                );
715
            }
716

                            
                        
717
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
718
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
719
            }
720
        } else {
721
            assertRevertReasonNotEqual(returnData, "Panic(17)");
722
        }
723
    }
724

                            
                        
725
    function addColl(uint _coll, uint256 _i) public setup {
726
        bool success;
727
        bytes memory returnData;
728

                            
                        
729
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
730
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
731

                            
                        
732
        _i = between(_i, 0, numberOfCdps - 1);
733
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
734
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
735

                            
                        
736
        _coll = between(_coll, 0, INITIAL_COLL_BALANCE / 10);
737

                            
                        
738
        if (collateral.balanceOf(address(actor)) < _coll) {
739
            (success, ) = actor.proxy(
740
                address(collateral),
741
                abi.encodeWithSelector(CollateralTokenTester.deposit.selector, ""),
742
                (_coll - collateral.balanceOf(address(actor)))
743
            );
744
            require(success);
745
            require(
746
                collateral.balanceOf(address(actor)) > _coll,
747
                "Actor has high enough balance to add"
748
            );
749
        }
750

                            
                        
751
        (success, ) = actor.proxy(
752
            address(collateral),
753
            abi.encodeWithSelector(
754
                CollateralTokenTester.approve.selector,
755
                address(borrowerOperations),
756
                _coll
757
            )
758
        );
759
        t(success, "Approve never fails");
760

                            
                        
761
        _before(_cdpId);
762

                            
                        
763
        (success, returnData) = actor.proxy(
764
            address(borrowerOperations),
765
            abi.encodeWithSelector(
766
                BorrowerOperations.addColl.selector,
767
                _cdpId,
768
                _cdpId,
769
                _cdpId,
770
                _coll
771
            )
772
        );
773

                            
                        
774
        _after(_cdpId);
775

                            
                        
776
        if (success) {
777
            emit L3(
778
                vars.isRecoveryModeBefore ? 1 : 0,
779
                vars.hasGracePeriodPassedBefore ? 1 : 0,
780
                vars.icrAfter
781
            );
782
            emit L3(
783
                block.timestamp,
784
                cdpManager.lastGracePeriodStartTimestamp(),
785
                cdpManager.recoveryModeGracePeriod()
786
            );
787

                            
                        
788
            eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
789
            gte(vars.nicrAfter, vars.nicrBefore, BO_03);
790
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
791
            t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
792
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
793
            gte(
794
                collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
795
                borrowerOperations.MIN_NET_COLL(),
796
                GENERAL_10
797
            );
798

                            
                        
799
            t(invariant_GENERAL_01(vars), GENERAL_01);
800

                            
                        
801
            if (
802
                vars.lastGracePeriodStartTimestampIsSetBefore &&
803
                vars.isRecoveryModeBefore &&
804
                vars.isRecoveryModeAfter
805
            ) {
806
                eq(
807
                    vars.lastGracePeriodStartTimestampBefore,
808
                    vars.lastGracePeriodStartTimestampAfter,
809
                    L_14
810
                );
811
            }
812

                            
                        
813
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
814
                t(
815
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
816
                        vars.lastGracePeriodStartTimestampIsSetAfter,
817
                    L_15
818
                );
819
            }
820

                            
                        
821
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
822
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
823
            }
824
        } else {
825
            assertRevertReasonNotEqual(returnData, "Panic(17)");
826
        }
827
    }
828

                            
                        
829
    function withdrawColl(uint _amount, uint256 _i) public setup {
830
        bool success;
831
        bytes memory returnData;
832

                            
                        
833
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
834
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
835

                            
                        
836
        _i = between(_i, 0, numberOfCdps - 1);
837
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
838
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
839

                            
                        
840
        // Can only withdraw up to CDP collateral amount, otherwise will revert with assert
841
        _amount = between(
842
            _amount,
843
            0,
844
            collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId))
845
        );
846

                            
                        
847
        _before(_cdpId);
848

                            
                        
849
        (success, returnData) = actor.proxy(
850
            address(borrowerOperations),
851
            abi.encodeWithSelector(
852
                BorrowerOperations.withdrawColl.selector,
853
                _cdpId,
854
                _amount,
855
                _cdpId,
856
                _cdpId
857
            )
858
        );
859

                            
                        
860
        _after(_cdpId);
861

                            
                        
862
        if (success) {
863
            eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
864
            lte(vars.nicrAfter, vars.nicrBefore, BO_04);
865
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
866
            t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
867
            t(invariant_GENERAL_01(vars), GENERAL_01);
868
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
869
            gte(
870
                collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
871
                borrowerOperations.MIN_NET_COLL(),
872
                GENERAL_10
873
            );
874

                            
                        
875
            if (
876
                vars.lastGracePeriodStartTimestampIsSetBefore &&
877
                vars.isRecoveryModeBefore &&
878
                vars.isRecoveryModeAfter
879
            ) {
880
                eq(
881
                    vars.lastGracePeriodStartTimestampBefore,
882
                    vars.lastGracePeriodStartTimestampAfter,
883
                    L_14
884
                );
885
            }
886

                            
                        
887
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
888
                t(
889
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
890
                        vars.lastGracePeriodStartTimestampIsSetAfter,
891
                    L_15
892
                );
893
            }
894

                            
                        
895
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
896
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
897
            }
898
        } else {
899
            assertRevertReasonNotEqual(returnData, "Panic(17)");
900
        }
901
    }
902

                            
                        
903
    function withdrawEBTC(uint _amount, uint256 _i) public setup {
904
        bool success;
905
        bytes memory returnData;
906

                            
                        
907
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
908
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
909

                            
                        
910
        _i = between(_i, 0, numberOfCdps - 1);
911
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
912
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
913

                            
                        
914
        // TODO verify the assumption below, maybe there's a more sensible (or Governance-defined/hardcoded) limit for the maximum amount of minted eBTC at a single operation
915
        // Can only withdraw up to type(uint128).max eBTC, so that `BorrwerOperations._getNewCdpAmounts` does not overflow
916
        _amount = between(_amount, 0, type(uint128).max);
917

                            
                        
918
        _before(_cdpId);
919

                            
                        
920
        (success, returnData) = actor.proxy(
921
            address(borrowerOperations),
922
            abi.encodeWithSelector(
923
                BorrowerOperations.withdrawEBTC.selector,
924
                _cdpId,
925
                _amount,
926
                _cdpId,
927
                _cdpId
928
            )
929
        );
930

                            
                        
931
        require(success);
932

                            
                        
933
        _after(_cdpId);
934

                            
                        
935
        eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
936
        gte(vars.cdpDebtAfter, vars.cdpDebtBefore, "withdrawEBTC must not decrease debt");
937
        eq(
938
            vars.actorEbtcAfter,
939
            vars.actorEbtcBefore + _amount,
940
            "withdrawEBTC must increase debt by requested amount"
941
        );
942
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
943
        gte(
944
            collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
945
            borrowerOperations.MIN_NET_COLL(),
946
            GENERAL_10
947
        );
948

                            
                        
949
        if (
950
            vars.lastGracePeriodStartTimestampIsSetBefore &&
951
            vars.isRecoveryModeBefore &&
952
            vars.isRecoveryModeAfter
953
        ) {
954
            eq(
955
                vars.lastGracePeriodStartTimestampBefore,
956
                vars.lastGracePeriodStartTimestampAfter,
957
                L_14
958
            );
959
        }
960

                            
                        
961
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
962
            t(
963
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
964
                    vars.lastGracePeriodStartTimestampIsSetAfter,
965
                L_15
966
            );
967
        }
968

                            
                        
969
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
970
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
971
        }
972
    }
973

                            
                        
974
    function repayEBTC(uint _amount, uint256 _i) public setup {
975
        bool success;
976
        bytes memory returnData;
977

                            
                        
978
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
979
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
980

                            
                        
981
        _i = between(_i, 0, numberOfCdps - 1);
982
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
983
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
984

                            
                        
985
        (uint256 entireDebt, , ) = cdpManager.getDebtAndCollShares(_cdpId);
986
        _amount = between(_amount, 0, entireDebt);
987

                            
                        
988
        _before(_cdpId);
989

                            
                        
990
        (success, returnData) = actor.proxy(
991
            address(borrowerOperations),
992
            abi.encodeWithSelector(
993
                BorrowerOperations.repayEBTC.selector,
994
                _cdpId,
995
                _amount,
996
                _cdpId,
997
                _cdpId
998
            )
999
        );
1000
        require(success);
1001

                            
                        
1002
        _after(_cdpId);
1003

                            
                        
1004
        eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
1005

                            
                        
1006
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
1007
        gte(vars.newTcrAfter, vars.newTcrBefore, BO_08);
1008

                            
                        
1009
        eq(vars.ebtcTotalSupplyBefore - _amount, vars.ebtcTotalSupplyAfter, BO_07);
1010
        eq(vars.actorEbtcBefore - _amount, vars.actorEbtcAfter, BO_07);
1011
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
1012
        t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
1013
        t(invariant_GENERAL_01(vars), GENERAL_01);
1014
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
1015
        gte(
1016
            collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
1017
            borrowerOperations.MIN_NET_COLL(),
1018
            GENERAL_10
1019
        );
1020

                            
                        
1021
        if (
1022
            vars.lastGracePeriodStartTimestampIsSetBefore &&
1023
            vars.isRecoveryModeBefore &&
1024
            vars.isRecoveryModeAfter
1025
        ) {
1026
            eq(
1027
                vars.lastGracePeriodStartTimestampBefore,
1028
                vars.lastGracePeriodStartTimestampAfter,
1029
                L_14
1030
            );
1031
        }
1032

                            
                        
1033
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
1034
            t(
1035
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
1036
                    vars.lastGracePeriodStartTimestampIsSetAfter,
1037
                L_15
1038
            );
1039
        }
1040

                            
                        
1041
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
1042
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
1043
        }
1044
    }
1045

                            
                        
1046
    function closeCdp(uint _i) public setup {
1047
        bool success;
1048
        bytes memory returnData;
1049

                            
                        
1050
        require(cdpManager.getActiveCdpsCount() > 1, "Cannot close last CDP");
1051

                            
                        
1052
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
1053
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
1054

                            
                        
1055
        _i = between(_i, 0, numberOfCdps - 1);
1056
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
1057
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
1058

                            
                        
1059
        _before(_cdpId);
1060

                            
                        
1061
        (success, returnData) = actor.proxy(
1062
            address(borrowerOperations),
1063
            abi.encodeWithSelector(BorrowerOperations.closeCdp.selector, _cdpId)
1064
        );
1065

                            
                        
1066
        _after(_cdpId);
1067

                            
                        
1068
        if (success) {
1069
            eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
1070
            eq(vars.cdpDebtAfter, 0, BO_02);
1071
            eq(
1072
                vars.sortedCdpsSizeBefore - 1,
1073
                vars.sortedCdpsSizeAfter,
1074
                "closeCdp reduces list size by 1"
1075
            );
1076
            gt(
1077
                vars.actorCollAfter,
1078
                vars.actorCollBefore,
1079
                "closeCdp increases the collateral balance of the user"
1080
            );
1081
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
1082
            t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
1083
            emit L4(
1084
                vars.actorCollBefore,
1085
                vars.cdpCollBefore,
1086
                vars.liquidatorRewardSharesBefore,
1087
                vars.actorCollAfter
1088
            );
1089
            gt(
1090
                // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/11
1091
                // Note: not checking for strict equality since split fee is difficult to calculate a-priori, so the CDP collateral value may not be sent back to the user in full
1092
                vars.actorCollAfter,
1093
                vars.actorCollBefore +
1094
                    // ActivePool transfer SHARES not ETH directly
1095
                    collateral.getPooledEthByShares(vars.liquidatorRewardSharesBefore),
1096
                BO_05
1097
            );
1098
            t(invariant_GENERAL_01(vars), GENERAL_01);
1099

                            
                        
1100
            if (
1101
                vars.lastGracePeriodStartTimestampIsSetBefore &&
1102
                vars.isRecoveryModeBefore &&
1103
                vars.isRecoveryModeAfter
1104
            ) {
1105
                eq(
1106
                    vars.lastGracePeriodStartTimestampBefore,
1107
                    vars.lastGracePeriodStartTimestampAfter,
1108
                    L_14
1109
                );
1110
            }
1111

                            
                        
1112
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
1113
                t(
1114
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
1115
                        vars.lastGracePeriodStartTimestampIsSetAfter,
1116
                    L_15
1117
                );
1118
            }
1119

                            
                        
1120
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
1121
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
1122
            }
1123
        } else {
1124
            assertRevertReasonNotEqual(returnData, "Panic(17)");
1125
        }
1126
    }
1127

                            
                        
1128
    function adjustCdp(
1129
        uint _i,
1130
        uint _collWithdrawal,
1131
        uint _EBTCChange,
1132
        bool _isDebtIncrease
1133
    ) public setup {
1134
        bool success;
1135
        bytes memory returnData;
1136

                            
                        
1137
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
1138
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
1139

                            
                        
1140
        _i = between(_i, 0, numberOfCdps - 1);
1141
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
1142
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
1143

                            
                        
1144
        (uint256 entireDebt, uint256 entireColl, ) = cdpManager.getDebtAndCollShares(_cdpId);
1145
        _collWithdrawal = between(_collWithdrawal, 0, entireColl);
1146
        _EBTCChange = between(_EBTCChange, 0, entireDebt);
1147

                            
                        
1148
        _before(_cdpId);
1149

                            
                        
1150
        (success, returnData) = actor.proxy(
1151
            address(borrowerOperations),
1152
            abi.encodeWithSelector(
1153
                BorrowerOperations.adjustCdp.selector,
1154
                _cdpId,
1155
                _collWithdrawal,
1156
                _EBTCChange,
1157
                _isDebtIncrease,
1158
                _cdpId,
1159
                _cdpId
1160
            )
1161
        );
1162

                            
                        
1163
        require(success);
1164

                            
                        
1165
        _after(_cdpId);
1166

                            
                        
1167
        eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
1168
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
1169
        t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
1170

                            
                        
1171
        t(invariant_GENERAL_01(vars), GENERAL_01);
1172
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
1173
        gte(
1174
            collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
1175
            borrowerOperations.MIN_NET_COLL(),
1176
            GENERAL_10
1177
        );
1178

                            
                        
1179
        if (
1180
            vars.lastGracePeriodStartTimestampIsSetBefore &&
1181
            vars.isRecoveryModeBefore &&
1182
            vars.isRecoveryModeAfter
1183
        ) {
1184
            eq(
1185
                vars.lastGracePeriodStartTimestampBefore,
1186
                vars.lastGracePeriodStartTimestampAfter,
1187
                L_14
1188
            );
1189
        }
1190

                            
                        
1191
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
1192
            t(
1193
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
1194
                    vars.lastGracePeriodStartTimestampIsSetAfter,
1195
                L_15
1196
            );
1197
        }
1198

                            
                        
1199
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
1200
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
1201
        }
1202
    }
1203

                            
                        
1204
    ///////////////////////////////////////////////////////
1205
    // Collateral Token (Test)
1206
    ///////////////////////////////////////////////////////
1207

                            
                        
1208
    // Example for real world slashing: https://twitter.com/LidoFinance/status/1646505631678107649
1209
    // > There are 11 slashing ongoing with the RockLogic GmbH node operator in Lido.
1210
    // > the total projected impact is around 20 ETH,
1211
    // > or about 3% of average daily protocol rewards/0.0004% of TVL.
1212
    function setEthPerShare(uint256 _newEthPerShare) public {
1213
        uint256 currentEthPerShare = collateral.getEthPerShare();
1214
        _newEthPerShare = between(
1215
            _newEthPerShare,
1216
            (currentEthPerShare * 1e18) / MAX_REBASE_PERCENT,
1217
            (currentEthPerShare * MAX_REBASE_PERCENT) / 1e18
1218
        );
1219
        collateral.setEthPerShare(_newEthPerShare);
1220
    }
1221

                            
                        
1222
    ///////////////////////////////////////////////////////
1223
    // PriceFeed
1224
    ///////////////////////////////////////////////////////
1225

                            
                        
1226
    function setPrice(uint256 _newPrice) public {
1227
        uint256 currentPrice = priceFeedMock.getPrice();
1228
        _newPrice = between(
1229
            _newPrice,
1230
            (currentPrice * 1e18) / MAX_PRICE_CHANGE_PERCENT,
1231
            (currentPrice * MAX_PRICE_CHANGE_PERCENT) / 1e18
1232
        );
1233
        priceFeedMock.setPrice(_newPrice);
1234
    }
1235

                            
                        
1236
    ///////////////////////////////////////////////////////
1237
    // Governance
1238
    ///////////////////////////////////////////////////////
1239

                            
                        
1240
    function setGovernanceParameters(uint256 parameter, uint256 value) public {
1241
        parameter = between(parameter, 0, 6);
1242

                            
                        
1243
        if (parameter == 0) {
1244
            value = between(value, cdpManager.MINIMUM_GRACE_PERIOD(), type(uint128).max);
1245
            hevm.prank(defaultGovernance);
1246
            cdpManager.setGracePeriod(uint128(value));
1247
        } else if (parameter == 1) {
1248
            value = between(value, 0, activePool.getFeeRecipientClaimableCollShares());
1249
            hevm.prank(defaultGovernance);
1250
            _before(bytes32(0));
1251
            activePool.claimFeeRecipientCollShares(value);
1252
            _after(bytes32(0));
1253
            // If there was something to claim
1254
            if(value > 0) {
1255
                // Claiming will increase the balance
1256
                gte(vars.feeRecipientCollSharesAfter, vars.feeRecipientCollSharesBefore, F_01);
1257
            }
1258
        } else if (parameter == 2) {
1259
            value = between(value, 0, cdpManager.MAX_REWARD_SPLIT());
1260
            hevm.prank(defaultGovernance);
1261
            cdpManager.setStakingRewardSplit(value);
1262
        } else if (parameter == 3) {
1263
            value = between(
1264
                value,
1265
                cdpManager.MIN_REDEMPTION_FEE_FLOOR(),
1266
                cdpManager.DECIMAL_PRECISION()
1267
            );
1268
            hevm.prank(defaultGovernance);
1269
            cdpManager.setRedemptionFeeFloor(value);
1270
        } else if (parameter == 4) {
1271
            value = between(
1272
                value,
1273
                cdpManager.MIN_MINUTE_DECAY_FACTOR(),
1274
                cdpManager.MAX_MINUTE_DECAY_FACTOR()
1275
            );
1276
            hevm.prank(defaultGovernance);
1277
            cdpManager.setMinuteDecayFactor(value);
1278
        } else if (parameter == 5) {
1279
            value = between(value, 0, cdpManager.DECIMAL_PRECISION());
1280
            hevm.prank(defaultGovernance);
1281
            cdpManager.setBeta(value);
1282
        } else if (parameter == 6) {
1283
            value = between(value, 0, 1);
1284
            hevm.prank(defaultGovernance);
1285
            cdpManager.setRedemptionsPaused(value == 1 ? true : false);
1286
        }
1287
    }
1288
}
1289

                            
                        

Lines covered: 8 / 8 (100.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import "@crytic/properties/contracts/util/PropertiesHelper.sol";
4
import "../Asserts.sol";
5

                            
                        
6
abstract contract EchidnaAsserts is PropertiesAsserts, Asserts {
7
    function gt(uint256 a, uint256 b, string memory message) internal override {
8
        assertGt(a, b, message);
9
    }
10

                            
                        
11
    function lt(uint256 a, uint256 b, string memory message) internal override {
12
        assertLt(a, b, message);
13
    }
14

                            
                        
15
    function gte(uint256 a, uint256 b, string memory message) internal override {
16
        assertGte(a, b, message);
17
    }
18

                            
                        
19
    function lte(uint256 a, uint256 b, string memory message) internal override {
20
        assertLte(a, b, message);
21
    }
22

                            
                        
23
    function eq(uint256 a, uint256 b, string memory message) internal override {
24
        assertEq(a, b, message);
25
    }
26

                            
                        
27
    function t(bool a, string memory message) internal override {
28
        assertWithMsg(a, message);
29
    }
30

                            
                        
31
    function between(uint256 value, uint256 low, uint256 high) internal override returns (uint256) {
32
        return clampBetween(value, low, high);
33
    }
34
}
35

                            
                        

Lines covered: 54 / 54 (100.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import {TargetContractSetup} from "../TargetContractSetup.sol";
4
import {Properties} from "../Properties.sol";
5

                            
                        
6
abstract contract EchidnaProperties is TargetContractSetup, Properties {
7
    function echidna_price() public returns (bool) {
8
        return invariant_DUMMY_01(priceFeedMock);
9
    }
10

                            
                        
11
    function echidna_active_pool_invariant_1() public returns (bool) {
12
        return invariant_AP_01(collateral, activePool);
13
    }
14

                            
                        
15
    function echidna_active_pool_invariant_2() public returns (bool) {
16
        return invariant_AP_02(cdpManager, activePool);
17
    }
18

                            
                        
19
    function echidna_active_pool_invariant_3() public returns (bool) {
20
        return invariant_AP_03(eBTCToken, activePool);
21
    }
22

                            
                        
23
    function echidna_active_pool_invariant_4() public returns (bool) {
24
        return invariant_AP_04(cdpManager, activePool, diff_tolerance);
25
    }
26

                            
                        
27
    function echidna_active_pool_invariant_5() public returns (bool) {
28
        return invariant_AP_05(cdpManager, diff_tolerance);
29
    }
30

                            
                        
31
    function echidna_cdp_manager_invariant_1() public returns (bool) {
32
        return invariant_CDPM_01(cdpManager, sortedCdps);
33
    }
34

                            
                        
35
    function echidna_cdp_manager_invariant_2() public returns (bool) {
36
        return invariant_CDPM_02(cdpManager);
37
    }
38

                            
                        
39
    function echidna_cdp_manager_invariant_3() public returns (bool) {
40
        return invariant_CDPM_03(cdpManager);
41
    }
42

                            
                        
43
    // CDPM_04 is a vars invariant
44

                            
                        
45
    function echidna_coll_surplus_pool_invariant_1() public returns (bool) {
46
        return invariant_CSP_01(collateral, collSurplusPool);
47
    }
48

                            
                        
49
    function echidna_coll_surplus_pool_invariant_2() public returns (bool) {
50
        return invariant_CSP_02(collSurplusPool);
51
    }
52

                            
                        
53
    function echidna_sorted_list_invariant_1() public returns (bool) {
54
        return invariant_SL_01(cdpManager, sortedCdps);
55
    }
56

                            
                        
57
    function echidna_sorted_list_invariant_2() public returns (bool) {
58
        return invariant_SL_02(cdpManager, sortedCdps, priceFeedMock);
59
    }
60

                            
                        
61
    function echidna_sorted_list_invariant_3() public returns (bool) {
62
        return invariant_SL_03(cdpManager, priceFeedMock, sortedCdps);
63
    }
64

                            
                        
65
    // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/15
66
    function echidna_sorted_list_invariant_5() public returns (bool) {
67
        return invariant_SL_05(crLens, sortedCdps);
68
    }
69

                            
                        
70
    // invariant_GENERAL_01 is a vars invariant
71

                            
                        
72
    function echidna_GENERAL_02() public returns (bool) {
73
        return invariant_GENERAL_02(cdpManager, priceFeedMock, eBTCToken);
74
    }
75

                            
                        
76
    function echidna_GENERAL_03() public returns (bool) {
77
        return invariant_GENERAL_03(cdpManager, borrowerOperations, eBTCToken, collateral);
78
    }
79

                            
                        
80
    function echidna_GENERAL_05() public returns (bool) {
81
        return invariant_GENERAL_05(activePool, cdpManager, collateral);
82
    }
83

                            
                        
84
    function echidna_GENERAL_05_B() public returns (bool) {
85
        return invariant_GENERAL_05_B(collSurplusPool, collateral);
86
    }
87

                            
                        
88
    function echidna_GENERAL_06() public returns (bool) {
89
        return invariant_GENERAL_06(eBTCToken, cdpManager, sortedCdps);
90
    }
91

                            
                        
92
    function echidna_GENERAL_08() public returns (bool) {
93
        return invariant_GENERAL_08(cdpManager, sortedCdps, priceFeedMock, collateral);
94
    }
95

                            
                        
96
    // invariant_GENERAL_09 is a vars
97

                            
                        
98
    function echidna_GENERAL_12() public returns (bool) {
99
        return invariant_GENERAL_12(cdpManager, priceFeedMock, crLens);
100
    }
101

                            
                        
102
    function echidna_GENERAL_13() public returns (bool) {
103
        return invariant_GENERAL_13(crLens, cdpManager, priceFeedMock, sortedCdps);
104
    }
105

                            
                        
106
    function echidna_GENERAL_14() public returns (bool) {
107
        return invariant_GENERAL_14(crLens, cdpManager, sortedCdps);
108
    }
109

                            
                        
110
    function echidna_LS_01() public returns (bool) {
111
        return
112
            invariant_LS_01(
113
                cdpManager,
114
                liquidationSequencer,
115
                syncedLiquidationSequencer,
116
                priceFeedMock
117
            );
118
    }
119
}
120

                            
                        

Lines covered: 3 / 3 (100.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./EchidnaAsserts.sol";
6
import "./EchidnaProperties.sol";
7
import "../TargetFunctions.sol";
8

                            
                        
9
contract EchidnaTester is EchidnaAsserts, EchidnaProperties, TargetFunctions {
10
    constructor() payable {
11
        _setUp();
12
        _setUpActors();
13
    }
14
}
15

                            
                        

Lines covered: 11 / 35 (31.4%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../../Interfaces/IPriceFeed.sol";
6
import "../../Interfaces/IFallbackCaller.sol";
7
import "../../Dependencies/Ownable.sol";
8
import "../../Dependencies/AuthNoOwner.sol";
9

                            
                        
10
/*
11
 * PriceFeed placeholder for testnet and development. The price can be manually input or fetched from
12
   the Fallback's TestNet implementation. Backwards compatible with local test environment as it defaults to use
13
   the manual price.
14
 */
15
contract PriceFeedTestnet is IPriceFeed, Ownable, AuthNoOwner {
16
    // --- variables ---
17

                            
                        
18
    uint256 private _price = 7428 * 1e13; // stETH/BTC price == ~15.8118 ETH per BTC
19
    bool public _useFallback;
20
    IFallbackCaller public fallbackCaller; // Wrapper contract that calls the Fallback system
21

                            
                        
22
    constructor(address _authorityAddress) {
23
        _initializeAuthority(_authorityAddress);
24
    }
25

                            
                        
26
    // --- Dependency setters ---
27

                            
                        
28
    function setAddresses(
29
        address _priceAggregatorAddress, // Not used but kept for compatibility with deployment script
30
        address _fallbackCallerAddress,
31
        address _authorityAddress
32
    ) external onlyOwner {
33
        fallbackCaller = IFallbackCaller(_fallbackCallerAddress);
34

                            
                        
35
        _initializeAuthority(_authorityAddress);
36

                            
                        
37
        renounceOwnership();
38
    }
39

                            
                        
40
    // --- Functions ---
41

                            
                        
42
    // View price getter for simplicity in tests
43
    function getPrice() external view returns (uint256) {
44
        return _price;
45
    }
46

                            
                        
47
    function fetchPrice() external override returns (uint256) {
48
        // Fire an event just like the mainnet version would.
49
        // This lets the subgraph rely on events to get the latest price even when developing locally.
50
        if (_useFallback) {
51
            FallbackResponse memory fallbackResponse = _getCurrentFallbackResponse();
52
            if (fallbackResponse.success) {
53
                _price = fallbackResponse.answer;
54
            }
55
        }
56
        emit LastGoodPriceUpdated(_price);
57
        return _price;
58
    }
59

                            
                        
60
    // Manual external price setter.
61
    function setPrice(uint256 price) external returns (bool) {
62
        _price = price;
63
        return true;
64
    }
65

                            
                        
66
    // Manual toggle use of Tellor testnet feed
67
    function toggleUseFallback() external returns (bool) {
68
        _useFallback = !_useFallback;
69
        return _useFallback;
70
    }
71

                            
                        
72
    function setFallbackCaller(address _fallbackCaller) external requiresAuth {
73
        address oldFallbackCaller = address(fallbackCaller);
74
        fallbackCaller = IFallbackCaller(_fallbackCaller);
75
        emit FallbackCallerChanged(oldFallbackCaller, _fallbackCaller);
76
    }
77

                            
                        
78
    // --- Oracle response wrapper functions ---
79
    /*
80
     * "_getCurrentFallbackResponse" fetches stETH/BTC from the Fallback, and returns it as a
81
     * FallbackResponse struct.
82
     */
83
    function _getCurrentFallbackResponse()
84
        internal
85
        view
86
        returns (FallbackResponse memory fallbackResponse)
87
    {
88
        uint256 stEthBtcValue;
89
        uint256 stEthBtcTimestamp;
90
        bool stEthBtcRetrieved;
91

                            
                        
92
        // Attempt to get the Fallback's stETH/BTC price
93
        try fallbackCaller.getFallbackResponse() returns (
94
            uint256 answer,
95
            uint256 timestampRetrieved,
96
            bool success
97
        ) {
98
            fallbackResponse.answer = answer;
99
            fallbackResponse.timestamp = timestampRetrieved;
100
            fallbackResponse.success = success;
101
        } catch {
102
            return (fallbackResponse);
103
        }
104
        return (fallbackResponse);
105
    }
106
}
107

                            
                        

Lines covered: 1 / 1 (100.0%)

1
// SPDX-License-Identifier: Unlicense
2
pragma solidity ^0.8.0;
3

                            
                        
4
interface IHevm {
5
    // Set block.timestamp to newTimestamp
6
    function warp(uint256 newTimestamp) external;
7

                            
                        
8
    // Set block.number to newNumber
9
    function roll(uint256 newNumber) external;
10

                            
                        
11
    // Loads a storage slot from an address
12
    function load(address where, bytes32 slot) external returns (bytes32);
13

                            
                        
14
    // Stores a value to an address' storage slot
15
    function store(address where, bytes32 slot, bytes32 value) external;
16

                            
                        
17
    // Signs data (privateKey, digest) => (r, v, s)
18
    function sign(uint256 privateKey, bytes32 digest) external returns (uint8 r, bytes32 v, bytes32 s);
19

                            
                        
20
    // Gets address for a given private key
21
    function addr(uint256 privateKey) external returns (address addr);
22

                            
                        
23
    // Performs a foreign function call via terminal
24
    function ffi(string[] calldata inputs) external returns (bytes memory result);
25
    
26
    // Performs the next smart contract call with specified `msg.sender`
27
    function prank(address newSender) external;
28
}
29

                            
                        
30
IHevm constant hevm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

Lines covered: 3 / 3 (100.0%)

1
pragma solidity ^0.8.0;
2

                            
                        
3
abstract contract PropertiesConstants {
4
    // Constant echidna addresses
5
    address constant USER1 = address(0x10000);
6
    address constant USER2 = address(0x20000);
7
    address constant USER3 = address(0x30000);
8
    uint256 constant INITIAL_BALANCE = 1000e18;
9
}
10

                            
                        

Lines covered: 41 / 57 (71.9%)

1
pragma solidity ^0.8.0;
2

                            
                        
3
abstract contract PropertiesAsserts {
4
    event LogUint256(string,uint256);
5
    event LogAddress(string, address);
6
    event LogString(string);
7

                            
                        
8
    event AssertFail(string);
9
    event AssertEqFail(string);
10
    event AssertNeqFail(string);
11
    event AssertGteFail(string);
12
    event AssertGtFail(string);
13
    event AssertLteFail(string);
14
    event AssertLtFail(string);
15

                            
                        
16
    function assertWithMsg(bool b, string memory reason) internal {
17
        if(!b){
18
            emit AssertFail(reason);
19
            assert(false);
20
        }
21
    }
22

                            
                        
23
    /// @notice asserts that a is equal to b. Violations are logged using reason.
24
    function assertEq(uint256 a, uint256 b, string memory reason) internal {
25
        if(a != b){
26
            string memory aStr = PropertiesLibString.toString(a);
27
            string memory bStr = PropertiesLibString.toString(b);
28
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"!=",bStr,", reason: ", reason);
29
            emit AssertEqFail(string(assertMsg));
30
            assert(false);
31
        }
32
    }
33

                            
                        
34
    /// @notice int256 version of assertEq
35
    function assertEq(int256 a, int256 b, string memory reason) internal {
36
        if(a != b){
37
            string memory aStr = PropertiesLibString.toString(a);
38
            string memory bStr = PropertiesLibString.toString(b);
39
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"!=",bStr,", reason: ", reason);
40
            emit AssertEqFail(string(assertMsg));
41
            assert(false);
42
        }
43
    }
44

                            
                        
45
    /// @notice asserts that a is not equal to b. Violations are logged using reason.
46
    function assertNeq(uint256 a, uint256 b, string memory reason) internal {
47
        if(a == b){
48
            string memory aStr = PropertiesLibString.toString(a);
49
            string memory bStr = PropertiesLibString.toString(b);
50
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"==",bStr,", reason: ", reason);
51
            emit AssertNeqFail(string(assertMsg));
52
            assert(false);
53
        }
54
    }
55

                            
                        
56
    /// @notice int256 version of assertNeq
57
    function assertNeq(int256 a, int256 b, string memory reason) internal {
58
        if(a == b){
59
            string memory aStr = PropertiesLibString.toString(a);
60
            string memory bStr = PropertiesLibString.toString(b);
61
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"==",bStr,", reason: ", reason);
62
            emit AssertNeqFail(string(assertMsg));
63
            assert(false);
64
        }
65
    }
66

                            
                        
67
    /// @notice asserts that a is greater than or equal to b. Violations are logged using reason.
68
    function assertGte(uint256 a, uint256 b, string memory reason) internal {
69
        if(!(a >= b)) {
70
            string memory aStr = PropertiesLibString.toString(a);
71
            string memory bStr = PropertiesLibString.toString(b);
72
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"<",bStr," failed, reason: ", reason);
73
            emit AssertGteFail(string(assertMsg));
74
            assert(false);
75
        }
76
    }
77

                            
                        
78
    /// @notice int256 version of assertGte
79
    function assertGte(int256 a, int256 b, string memory reason) internal {
80
        if(!(a >= b)) {
81
            string memory aStr = PropertiesLibString.toString(a);
82
            string memory bStr = PropertiesLibString.toString(b);
83
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"<",bStr," failed, reason: ", reason);
84
            emit AssertGteFail(string(assertMsg));
85
            assert(false);
86
        }
87
    }
88

                            
                        
89
    /// @notice asserts that a is greater than b. Violations are logged using reason.
90
    function assertGt(uint256 a, uint256 b, string memory reason) internal {
91
        if(!(a > b)) {
92
            string memory aStr = PropertiesLibString.toString(a);
93
            string memory bStr = PropertiesLibString.toString(b);
94
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"<=", bStr," failed, reason: ", reason);
95
            emit AssertGtFail(string(assertMsg));
96
            assert(false);
97
        }
98
    }
99

                            
                        
100
    /// @notice int256 version of assertGt
101
    function assertGt(int256 a, int256 b, string memory reason) internal {
102
        if(!(a > b)) {
103
            string memory aStr = PropertiesLibString.toString(a);
104
            string memory bStr = PropertiesLibString.toString(b);
105
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"<=", bStr," failed, reason: ", reason);
106
            emit AssertGtFail(string(assertMsg));
107
            assert(false);
108
        }
109
    }
110

                            
                        
111
    /// @notice asserts that a is less than or equal to b. Violations are logged using reason.
112
    function assertLte(uint256 a, uint256 b, string memory reason) internal {
113
        if(!(a <= b)) {
114
            string memory aStr = PropertiesLibString.toString(a);
115
            string memory bStr = PropertiesLibString.toString(b);
116
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,">", bStr," failed, reason: ", reason);
117
            emit AssertLteFail(string(assertMsg));
118
            assert(false);
119
        }
120
    }
121

                            
                        
122
    /// @notice int256 version of assertLte
123
    function assertLte(int256 a, int256 b, string memory reason) internal {
124
        if(!(a <= b)) {
125
            string memory aStr = PropertiesLibString.toString(a);
126
            string memory bStr = PropertiesLibString.toString(b);
127
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,">", bStr," failed, reason: ", reason);
128
            emit AssertLteFail(string(assertMsg));
129
            assert(false);
130
        }
131
    }
132

                            
                        
133
    /// @notice asserts that a is less than b. Violations are logged using reason.
134
    function assertLt(uint256 a, uint256 b, string memory reason) internal {
135
        if(!(a < b)) {
136
            string memory aStr = PropertiesLibString.toString(a);
137
            string memory bStr = PropertiesLibString.toString(b);
138
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,">=",bStr," failed, reason: ", reason);
139
            emit AssertLtFail(string(assertMsg));
140
            assert(false);
141
        }
142
    }
143

                            
                        
144
    /// @notice int256 version of assertLt
145
    function assertLt(int256 a, int256 b, string memory reason) internal {
146
        if(!(a < b)) {
147
            string memory aStr = PropertiesLibString.toString(a);
148
            string memory bStr = PropertiesLibString.toString(b);
149
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,">=",bStr," failed, reason: ", reason);
150
            emit AssertLtFail(string(assertMsg));
151
            assert(false);
152
        }
153
    }
154

                            
                        
155
    /// @notice Clamps value to be between low and high, both inclusive
156
    function clampBetween(uint256 value, uint256 low, uint256 high) internal returns (uint256) {
157
        if(value < low || value > high) {
158
            uint ans = low + (value % (high - low + 1));
159
            string memory valueStr = PropertiesLibString.toString(value);
160
            string memory ansStr = PropertiesLibString.toString(ans);
161
            bytes memory message = abi.encodePacked("Clamping value ", valueStr, " to ", ansStr);
162
            emit LogString(string(message));
163
            return ans;
164
        }
165
        return value;
166
    }
167

                            
                        
168
    /// @notice int256 version of clampBetween
169
    function clampBetween(int256 value, int256 low, int256 high) internal returns (int256) {
170
        if(value < low || value > high) {
171
            int range = high - low + 1;
172
            int clamped = (value - low) % (range);
173
            if (clamped < 0) clamped += range;
174
            int ans = low + clamped;
175
            string memory valueStr = PropertiesLibString.toString(value);
176
            string memory ansStr = PropertiesLibString.toString(ans);
177
            bytes memory message = abi.encodePacked("Clamping value ", valueStr, " to ", ansStr);
178
            emit LogString(string(message));
179
            return ans;
180
        }
181
        return value;
182
    }
183

                            
                        
184
    /// @notice clamps a to be less than b
185
    function clampLt(uint256 a, uint256 b) internal returns (uint256){
186
        if ( !(a < b)) {
187
            assertNeq(b, 0, "clampLt cannot clamp value a to be less than zero. Check your inputs/assumptions.");
188
            uint256 value = a % b;
189
            string memory aStr = PropertiesLibString.toString(a);
190
            string memory valueStr = PropertiesLibString.toString(value);
191
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
192
            emit LogString(string(message));
193
            return value;
194
        }
195
        return a;
196
    }
197

                            
                        
198
    /// @notice int256 version of clampLt
199
    function clampLt(int256 a, int256 b) internal returns (int256){
200
        if ( !(a < b)) {
201
            int256 value = b-1;
202
            string memory aStr = PropertiesLibString.toString(a);
203
            string memory valueStr = PropertiesLibString.toString(value);
204
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
205
            emit LogString(string(message));
206
            return value;
207
        }
208
        return a;
209
    }
210

                            
                        
211
    /// @notice clamps a to be less than or equal to b
212
    function clampLte(uint256 a, uint256 b) internal returns (uint256) {
213
        if(!(a <= b)) {
214
            uint256 value = a % (b+1);
215
            string memory aStr = PropertiesLibString.toString(a);
216
            string memory valueStr = PropertiesLibString.toString(value);
217
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
218
            emit LogString(string(message));
219
            return value;
220
        }
221
        return a;
222
    }
223

                            
                        
224
    /// @notice int256 version of clampLte
225
    function clampLte(int256 a, int256 b) internal returns (int256) {
226
        if(!(a <= b)) {
227
            int256 value = b;
228
            string memory aStr = PropertiesLibString.toString(a);
229
            string memory valueStr = PropertiesLibString.toString(value);
230
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
231
            emit LogString(string(message));
232
            return value;
233
        }
234
        return a;
235
    }
236

                            
                        
237
    /// @notice clamps a to be greater than b
238
    function clampGt(uint256 a, uint256 b) internal returns (uint256) {
239
        if(!(a > b)){
240
            assertNeq(b, type(uint256).max, "clampGt cannot clamp value a to be larger than uint256.max. Check your inputs/assumptions.");
241
            uint256 value = b+1;
242
            string memory aStr = PropertiesLibString.toString(a);
243
            string memory valueStr = PropertiesLibString.toString(value);
244
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
245
            emit LogString(string(message));
246
            return value;
247
        } else {
248
            return a;
249
        }
250
    }
251

                            
                        
252
    /// @notice int256 version of clampGt
253
    function clampGt(int256 a, int256 b) internal returns (int256) {
254
        if(!(a > b)){
255
            int256 value = b+1;
256
            string memory aStr = PropertiesLibString.toString(a);
257
            string memory valueStr = PropertiesLibString.toString(value);
258
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
259
            emit LogString(string(message));
260
            return value;
261
        } else {
262
            return a;
263
        }
264
    }
265

                            
                        
266
    /// @notice clamps a to be greater than or equal to b
267
    function clampGte(uint256 a, uint256 b) internal returns (uint256) {
268
        if(!(a > b)){
269
            uint256 value = b;
270
            string memory aStr = PropertiesLibString.toString(a);
271
            string memory valueStr = PropertiesLibString.toString(value);
272
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
273
            emit LogString(string(message));
274
            return value;
275
        }
276
        return a;
277
    }
278

                            
                        
279
    /// @notice int256 version of clampGte
280
    function clampGte(int256 a, int256 b) internal returns (int256) {
281
        if(!(a > b)){
282
            int256 value = b;
283
            string memory aStr = PropertiesLibString.toString(a);
284
            string memory valueStr = PropertiesLibString.toString(value);
285
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
286
            emit LogString(string(message));
287
            return value;
288
        }
289
        return a;
290
    }
291
}
292

                            
                        
293
/// @notice Efficient library for creating string representations of integers.
294
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
295
/// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol)
296
/// @dev Name of the library is modified to prevent collisions with contract-under-test uses of LibString
297
library PropertiesLibString {
298

                            
                        
299
    function toString(int256 value) internal pure returns (string memory str) {
300
        uint256 absValue = value >= 0 ? uint256(value) : uint256(-value);
301
        str = toString(absValue);
302

                            
                        
303
        if(value < 0) {
304
            str = string(abi.encodePacked("-", str));
305
        }
306
    }
307

                            
                        
308
    function toString(uint256 value) internal pure returns (string memory str) {
309
        /// @solidity memory-safe-assembly
310
        assembly {
311
            // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes
312
            // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the
313
            // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes.
314
            let newFreeMemoryPointer := add(mload(0x40), 160)
315

                            
                        
316
            // Update the free memory pointer to avoid overriding our string.
317
            mstore(0x40, newFreeMemoryPointer)
318

                            
                        
319
            // Assign str to the end of the zone of newly allocated memory.
320
            str := sub(newFreeMemoryPointer, 32)
321

                            
                        
322
            // Clean the last word of memory it may not be overwritten.
323
            mstore(str, 0)
324

                            
                        
325
            // Cache the end of the memory to calculate the length later.
326
            let end := str
327

                            
                        
328
            // We write the string from rightmost digit to leftmost digit.
329
            // The following is essentially a do-while loop that also handles the zero case.
330
            // prettier-ignore
331
            for { let temp := value } 1 {} {
332
                // Move the pointer 1 byte to the left.
333
                str := sub(str, 1)
334

                            
                        
335
                // Write the character to the pointer.
336
                // The ASCII index of the '0' character is 48.
337
                mstore8(str, add(48, mod(temp, 10)))
338

                            
                        
339
                // Keep dividing temp until zero.
340
                temp := div(temp, 10)
341

                            
                        
342
                 // prettier-ignore
343
                if iszero(temp) { break }
344
            }
345

                            
                        
346
            // Compute and cache the final total length of the string.
347
            let length := sub(end, str)
348

                            
                        
349
            // Move the pointer 32 bytes leftwards to make room for the length.
350
            str := sub(str, 32)
351

                            
                        
352
            // Store the string's length at the start of memory allocated for our string.
353
            mstore(str, length)
354
        }
355
    }
356

                            
                        
357
    function toString(address value) internal pure returns (string memory str){
358
        bytes memory s = new bytes(40);
359
        for (uint i = 0; i < 20; i++) {
360
            bytes1 b = bytes1(uint8(uint(uint160(value)) / (2**(8*(19 - i)))));
361
            bytes1 hi = bytes1(uint8(b) / 16);
362
            bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
363
            s[2*i] = char(hi);
364
            s[2*i+1] = char(lo);            
365
        }
366
        return string(s);
367
    }
368

                            
                        
369
    function char(bytes1 b) internal pure returns (bytes1 c) {
370
        if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
371
        else return bytes1(uint8(b) + 0x57);
372
    }
373
}
374